mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 02:14:49 +00:00
Merge remote-tracking branch 'origin/main' into release-1.7
Conflicts: CHANGELOG.md
This commit is contained in:
commit
d3441d3e23
69 files changed
+3201
-1259
No files matched your search
@@ -52,6 +52,8 @@ jobs:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
|
||||
@@ -66,12 +66,12 @@ jobs:
|
||||
# test can fall through to "no batch". To add/rebalance: edit a MARKERS_* var, and (for a new
|
||||
# group) add a case branch below + the batch name to the matrix list.
|
||||
env:
|
||||
MARKERS_JVM: "java or csharp or fsharp or kotlin or scala or groovy or clojure or bsl"
|
||||
MARKERS_JVM: "java or csharp or fsharp or kotlin or scala or groovy or clojure or bsl or nextflow"
|
||||
MARKERS_NATIVE: "cpp or rust or go or zig or pascal or swift" # swift is macOS-only (skips elsewhere via the guard)
|
||||
# other-langs: grab-bag + scripting languages + haskell + qml. Haskell (GHC/HLS) and qml (qmlls) are
|
||||
# installed only on Linux -- too slow/expensive to build on the macOS runner (Haskell), or not worth
|
||||
# the cost of a cross-OS Qt pull (qmlls) -- and skip on Windows+macOS via the central conftest guard.
|
||||
MARKERS_OTHER_LANGS: "ruby or php or lua or luau or bash or powershell or elixir or erlang or dart or haxe or haskell or terraform or rego or ansible or yaml or toml or markdown or latex or crystal or cue or fortran or ada or matlab or systemverilog or hlsl or msl or al or qml"
|
||||
MARKERS_OTHER_LANGS: "ruby or php or lua or luau or bash or powershell or elixir or erlang or dart or deno or haxe or haskell or terraform or rego or ansible or yaml or toml or markdown or latex or crystal or cue or fortran or ada or matlab or systemverilog or hlsl or msl or al or qml or gleam"
|
||||
# niche: slow, mostly-cached toolchains. ocaml + haskell are the two slowest -- kept in separate
|
||||
# batches (ocaml here, haskell in other-langs). nix + perl skip on Windows via the guard.
|
||||
MARKERS_NICHE: "julia or r or perl or lean4 or nix or ocaml"
|
||||
@@ -219,6 +219,13 @@ jobs:
|
||||
with:
|
||||
terraform_version: "1.5.0"
|
||||
terraform_wrapper: false
|
||||
# Deno ships its language server with the CLI (`deno lsp`), so installing the
|
||||
# runtime is all that is needed for the deno-marked tests.
|
||||
- name: Install Deno
|
||||
if: matrix.batch == 'other-langs'
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
# - name: Install swift
|
||||
# if: runner.os != 'Windows'
|
||||
# uses: swift-actions/setup-swift@v2
|
||||
@@ -446,6 +453,24 @@ jobs:
|
||||
missing="$(ldd "$QMLLS_DIR/qmlls" | grep 'not found' || true)"
|
||||
[ -z "$missing" ] || { echo "ERROR: qmlls has unresolved runtime libs:"; echo "$missing"; exit 1; }
|
||||
echo "$QMLLS_DIR" >> "$GITHUB_PATH"
|
||||
- name: Install Gleam compiler
|
||||
# Gleam's LSP is bundled with the compiler (started via `gleam lsp`). Restricted to Linux like
|
||||
# qmlls; Gleam skips on Windows/macOS CI via the conftest guard. The compiler is a self-contained
|
||||
# Rust binary (x86_64-unknown-linux-musl static build) with no runtime deps.
|
||||
if: matrix.batch == 'other-langs' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
GLEAM_VERSION="1.17.0"
|
||||
GLEAM_DIR="$HOME/.gleam"
|
||||
mkdir -p "$GLEAM_DIR"
|
||||
curl -fsSL -o /tmp/gleam.tar.gz "https://github.com/gleam-lang/gleam/releases/download/v${GLEAM_VERSION}/gleam-v${GLEAM_VERSION}-x86_64-unknown-linux-musl.tar.gz"
|
||||
tar xzf /tmp/gleam.tar.gz -C "$GLEAM_DIR"
|
||||
rm /tmp/gleam.tar.gz
|
||||
chmod +x "$GLEAM_DIR/gleam"
|
||||
# Fail loudly if any shared lib is unresolved (the musl build should be fully static).
|
||||
missing="$(ldd "$GLEAM_DIR/gleam" | grep 'not found' || true)"
|
||||
[ -z "$missing" ] || { echo "ERROR: gleam has unresolved runtime libs:"; echo "$missing"; exit 1; }
|
||||
echo "$GLEAM_DIR" >> "$GITHUB_PATH"
|
||||
- name: Cache Free Pascal Compiler
|
||||
if: matrix.batch == 'native'
|
||||
id: cache-fpc
|
||||
@@ -706,7 +731,11 @@ jobs:
|
||||
id: cache-language-servers
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.serena/language_servers/static
|
||||
# JDTLS workspaces contain mutable Eclipse/Buildship runtime state.
|
||||
# Cache the installed servers, but create each JDTLS workspace locally.
|
||||
path: |
|
||||
~/.serena/language_servers/static
|
||||
!~/.serena/language_servers/static/EclipseJDTLS/workspaces/**
|
||||
key: language-servers-${{ runner.os }}-${{ matrix.batch }}-v1
|
||||
restore-keys: |
|
||||
language-servers-${{ runner.os }}-${{ matrix.batch }}-
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# Conventions
|
||||
|
||||
## Style (project-instructed)
|
||||
- Idiomatic, object-oriented design. Non-trivial interfaces use **explicitly typed abstractions** (strategy pattern etc.) rather than bare functions/callbacks.
|
||||
- Avoid low-level data structures where an OO abstraction fits. For simple data containers use **dataclasses**, not dicts/tuples.
|
||||
- Structure function bodies into **functional blocks separated by blank lines**, each prefixed with a short elliptical phrase (lowercase, no leading capital) describing the block's purpose.
|
||||
- **Docstrings: reStructuredText.** Param/return/raises use `:param x:`, `:return:`, `:raises X:`.
|
||||
- Parameter / method / class descriptions begin with a precise elliptical phrase defining *what* the thing is; details in subsequent sentences.
|
||||
|
||||
## Formatting / lint (ruff)
|
||||
- Line length 140, double quotes, target `py311`.
|
||||
- Many "annoying" rules are disabled — see `[tool.ruff.lint] ignore` in `pyproject.toml` before adding workarounds (e.g. `Optional[T]` is preferred over `T | None`, `Union` is allowed, relative imports forbidden, `% string formatting` allowed).
|
||||
- `ruff format` runs on `src scripts test`; same set for `ruff check`.
|
||||
- mccabe complexity cap: 20.
|
||||
|
||||
## Typing (ty)
|
||||
- Type checker is **ty** (Astral), configured under `[tool.ty]` in `pyproject.toml`.
|
||||
|
||||
## Tests
|
||||
- Language-server tests are pytest-marker-gated (one marker per language; see `pyproject.toml` `[tool.pytest.ini_options].markers`). Default `poe test` runs unmarked tests + whatever `PYTEST_MARKERS` selects.
|
||||
- Snapshot tests use **syrupy** with custom `--snapshot-patch-pycharm-diff` plugin (auto-added via `addopts`).
|
||||
|
||||
## Tool descriptions (LLM-facing)
|
||||
- To change how a tool is described to the model, edit the Tool class's `apply()` **docstring** (in
|
||||
`src/serena/tools/*.py`) — `make_mcp_tool` parses the docstring body + `:param:` lines into the MCP tool
|
||||
description/schema. Do NOT use `tool_description_overrides` in context ymls (e.g. `claude-code.yml`); the
|
||||
docstring is the single source of truth and overrides drift. Keep `:param:` lines accurate.
|
||||
|
||||
## Memories
|
||||
- Follow `mem:memory_maintenance` for any new/updated memory in `.serena/memories/`.
|
||||
- Durable knowledge goes in memories/docs (not the assistant's auto-memory); see the monorepo top-level
|
||||
`CLAUDE.md` for the cross-repo conventions (no benchmark-tuning of agent-facing strings, etc.).
|
||||
@@ -0,0 +1,48 @@
|
||||
# Software Design
|
||||
|
||||
IMPORTANT: You use an idiomatic, object-oriented style (Java-esque principles, Pythonic syntax and constructs).
|
||||
|
||||
* You keep each concern in exactly one home.
|
||||
A mechanism whose parts are only correct in combination is implemented as a single component/class, and its parts are private to it;
|
||||
expose a minimal public surface. Java-like encapsulation: If helper functions and constants are only used by one abstraction,
|
||||
make them internal to the respective class.
|
||||
* You enforce invariants through structure and visibility; interfaces should not permit states the design forbids.
|
||||
* For any non-trivial interfaces, you use interfaces that expect explicitly typed abstractions
|
||||
rather than mere functions (i.e. use the strategy pattern, for example).
|
||||
You avoid the use of low-level data structures in all cases where an object-oriented abstraction would be more appropriate.
|
||||
For simple data storage, you use dataclasses instead of dictionaries or tuples.
|
||||
|
||||
# Testing
|
||||
|
||||
The key principle is to test *only* externally observable behavior and guarantees, never implementation structure.
|
||||
* Litmus test: a behaviour-preserving refactoring must not break any test. A test that could break is wrong and must not be written.
|
||||
* When functionality is removed, you delete its tests. You never add tests asserting the *absence* of something;
|
||||
absence of an implementation detail is not a behaviour, and such tests only freeze the current implementation and burden maintenance.
|
||||
* Fewer, behaviour-anchored tests are preferred; a missing test is better than an implementation-coupled one.
|
||||
|
||||
Language-server tests are pytest-marker-gated (one marker per language; see `pyproject.toml` `[tool.pytest.ini_options].markers`). Default `poe test` runs unmarked tests + whatever `PYTEST_MARKERS` selects.
|
||||
Snapshot tests use syrupy.
|
||||
|
||||
# Docstrings & Comments
|
||||
|
||||
* You consistently use reStructuredText.
|
||||
* You structure function implementations into functional blocks that are separated by blank lines.
|
||||
Atop each functional block, you write an elliptical phrase (starting with lower-case letter) that describes the purpose of the
|
||||
block in a concise manner.
|
||||
* When describing parameters, methods/functions and classes, you use a precise style, where the initial (elliptical) phrase
|
||||
clearly defines *what* it is. Any details then follow in subsequent sentences.
|
||||
* Each piece of information appears exactly once, at the element that owns it: callers do not
|
||||
explain callees' internals, and callees do not describe their callers.
|
||||
|
||||
# Pull requests
|
||||
|
||||
Read `mem:creating_pull_requests` when asked to participate in the creation of a pull request.
|
||||
|
||||
# Memories
|
||||
|
||||
- Follow `mem:memory_maintenance` for any new/updated memory in `.serena/memories/`.
|
||||
- Durable project knowledge goes into memories or docs/
|
||||
|
||||
# Dev Tools
|
||||
|
||||
Read `mem:task_completion` for tools to call upon task completion.
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
- Core principle: progressive discovery through references, building a graph of memories.
|
||||
- Initially, agents are provided with the list of all memories (names only).
|
||||
- Agents should read `mem:core` as the top-level entry point (graph root).
|
||||
- Agents should read `mem:critical_info` as the top-level entry point (graph root).
|
||||
This memory should contain references to other memories covering major project domains.
|
||||
The referenced memories shall, in turn, shall contain references to even more specific memories, and so on.
|
||||
The depth of the graph shall depend on the project complexity.
|
||||
|
||||
@@ -18,12 +18,10 @@ Serena is an MCP-based "IDE for coding agents": semantic code retrieval/editing/
|
||||
- `test/serena/`, `test/solidlsp/<lang>/` — pytest suites; per-language tests gated by pytest markers
|
||||
- `test/resources/repos/<lang>/` — fixture projects used by language-server tests
|
||||
- `scripts/` — utilities (prompt regen, tool overview, profiling, agno agent)
|
||||
- `docs/` — Jupyter Book sources; build via poe `doc-build`
|
||||
- `docs/` — Jupyter Book sources; build via `poe doc-build`
|
||||
|
||||
## Project-wide invariants
|
||||
|
||||
- Package name (PyPI): `serena-agent`; import root: `serena`. Wheel includes `serena`, `interprompt`, `solidlsp`.
|
||||
- Package name (PyPI): `serena-agent`; Wheel includes `serena`, `interprompt`, `solidlsp`.
|
||||
- Python: `>=3.11, <3.15`. Dependencies are exact-pinned in `pyproject.toml` (uvx installs from git, lockfile ignored — pin exactly).
|
||||
- Entry points: `serena` → `serena.cli:top_level`; `serena-hooks` → `serena.hooks:hook_commands`.
|
||||
- Per-project state lives under `<project>/.serena/` (config + `memories/` as `.md` files).
|
||||
- See `mem:tech_stack`, `mem:suggested_commands`, `mem:conventions`, `mem:task_completion`.
|
||||
@@ -1,113 +0,0 @@
|
||||
# Serena Repository Structure
|
||||
|
||||
## Overview
|
||||
Serena is a multi-language code assistant that combines two main components:
|
||||
1. **Serena Core** - The main agent framework with tools and MCP server
|
||||
2. **SolidLSP** - A unified Language Server Protocol wrapper for multiple programming languages
|
||||
|
||||
## Top-Level Structure
|
||||
|
||||
```
|
||||
serena/
|
||||
├── src/ # Main source code
|
||||
│ ├── serena/ # Serena agent framework
|
||||
│ ├── solidlsp/ # LSP wrapper library
|
||||
│ └── interprompt/ # Multi-language prompt templates
|
||||
├── test/ # Test suites
|
||||
│ ├── serena/ # Serena agent tests
|
||||
│ ├── solidlsp/ # Language server tests
|
||||
│ └── resources/repos/ # Test repositories for each language
|
||||
├── scripts/ # Build and utility scripts
|
||||
├── resources/ # Static resources and configurations
|
||||
├── pyproject.toml # Python project configuration
|
||||
├── README.md # Project documentation
|
||||
└── CHANGELOG.md # Version history
|
||||
```
|
||||
|
||||
## Source Code Organization
|
||||
|
||||
### Serena Core (`src/serena/`)
|
||||
- **`agent.py`** - Main SerenaAgent class that orchestrates everything
|
||||
- **`tools/`** - MCP tools for file operations, symbols, memory, etc.
|
||||
- `file_tools.py` - File system operations (read, write, search)
|
||||
- `symbol_tools.py` - Symbol-based code operations (find, edit)
|
||||
- `memory_tools.py` - Knowledge persistence and retrieval
|
||||
- `config_tools.py` - Project and mode management
|
||||
- `workflow_tools.py` - Onboarding and meta-operations
|
||||
- **`config/`** - Configuration management
|
||||
- `serena_config.py` - Main configuration classes
|
||||
- `context_mode.py` - Context and mode definitions
|
||||
- **`util/`** - Utility modules
|
||||
- **`mcp.py`** - MCP server implementation
|
||||
- **`cli.py`** - Command-line interface
|
||||
|
||||
### SolidLSP (`src/solidlsp/`)
|
||||
- **`ls.py`** - Main SolidLanguageServer class
|
||||
- **`language_servers/`** - Language-specific implementations
|
||||
- `csharp_language_server.py` - C# (Microsoft.CodeAnalysis.LanguageServer)
|
||||
- `python_server.py` - Python (Pyright)
|
||||
- `typescript_language_server.py` - TypeScript
|
||||
- `rust_analyzer.py` - Rust
|
||||
- `gopls.py` - Go
|
||||
- And many more...
|
||||
- **`ls_config.py`** - Language server configuration
|
||||
- **`ls_types.py`** - LSP type definitions
|
||||
- **`ls_utils.py`** - Utilities for working with LSP data
|
||||
|
||||
### Interprompt (`src/interprompt/`)
|
||||
- Multi-language prompt template system
|
||||
- Jinja2-based templating with language fallbacks
|
||||
|
||||
## Test Structure
|
||||
|
||||
### Language Server Tests (`test/solidlsp/`)
|
||||
Each language has its own test directory:
|
||||
```
|
||||
test/solidlsp/
|
||||
├── csharp/
|
||||
│ └── test_csharp_basic.py
|
||||
├── python/
|
||||
│ └── test_python_basic.py
|
||||
├── typescript/
|
||||
│ └── test_typescript_basic.py
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Test Resources (`test/resources/repos/`)
|
||||
Contains minimal test projects for each language:
|
||||
```
|
||||
test/resources/repos/
|
||||
├── csharp/test_repo/
|
||||
│ ├── serena.sln
|
||||
│ ├── TestProject.csproj
|
||||
│ ├── Program.cs
|
||||
│ └── Models/Person.cs
|
||||
├── python/test_repo/
|
||||
├── typescript/test_repo/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Test Infrastructure
|
||||
- **`test/conftest.py`** - Shared test fixtures and utilities
|
||||
- **`create_ls()`** function - Creates language server instances for testing
|
||||
- **`language_server` fixture** - Parametrized fixture for multi-language tests
|
||||
|
||||
## Key Configuration Files
|
||||
|
||||
- **`pyproject.toml`** - Python dependencies, build config, and tool settings
|
||||
- **`.serena/`** directories - Project-specific Serena configuration and memories
|
||||
- **`CLAUDE.md`** - Instructions for AI assistants working on the project
|
||||
|
||||
## Dependencies Management
|
||||
|
||||
The project uses modern Python tooling:
|
||||
- **uv** for fast dependency resolution and virtual environments
|
||||
- **pytest** for testing with language-specific markers (`@pytest.mark.csharp`)
|
||||
- **ruff** for linting and formatting
|
||||
- **ty** (Astral) for type checking
|
||||
|
||||
## Build and Development
|
||||
|
||||
- **Docker support** - Full containerized development environment
|
||||
- **GitHub Actions** - CI/CD with language server testing
|
||||
- **Development scripts** in `scripts/` directory
|
||||
@@ -1,24 +0,0 @@
|
||||
# Suggested Commands
|
||||
|
||||
Run via `uv run poe <task>` (or `poe <task>` inside the activated venv). Poe executor is `simple`, so plain `poe` works without uv re-resolving.
|
||||
|
||||
## Dev loop
|
||||
- `poe test` — pytest on `test/` (per-language tests are marker-gated; pass `-m <marker>` to enable).
|
||||
- `poe lint` — ruff format-check + ruff check (no fixes).
|
||||
- `poe format` — ruff `--fix` then `ruff format` (mutates files).
|
||||
- `poe type-check` — ty on `src/serena`, `src/solidlsp`, and `test/` (test pass relaxes pytest/mock-noisy rules via `[[tool.ty.overrides]]`).
|
||||
- Single test file: `uv run pytest test/path/to/test_x.py -vv`. Language-gated: add `-m python` etc.
|
||||
|
||||
## Docs
|
||||
- `poe doc-build` — clean + autogen + sphinx (uses `rm -rf`; needs a unix-like shell, e.g. Git Bash on Windows).
|
||||
|
||||
## Entrypoints
|
||||
- `uv run serena ...` — main CLI (`serena.cli:top_level`).
|
||||
- `uv run serena-hooks ...` — hook helpers.
|
||||
- `python scripts/gen_prompt_factory.py` — regenerate `src/serena/generated/generated_prompt_factory.py` after editing prompt templates.
|
||||
|
||||
## Windows shell notes (PowerShell 7+ is the project shell)
|
||||
- Use `Remove-Item -Recurse -Force <path>` instead of `rm -rf` (the `doc-clean` poe task uses unix `rm -rf` and requires bash).
|
||||
- Env vars: `$env:NAME = 'value'` (not `export`).
|
||||
- Path separator in `pyproject.toml` poe tasks uses forward slashes; PowerShell accepts them in arguments.
|
||||
- `git`, `uv`, `poe`, `pytest` behave identically to unix.
|
||||
@@ -8,4 +8,4 @@ After any code change in `src/` or `test/`, run:
|
||||
|
||||
If prompt templates changed: `uv run python scripts/gen_prompt_factory.py` (regenerates `src/serena/generated/generated_prompt_factory.py`; use `uv run poe format` and commit the result).
|
||||
|
||||
If memories were edited/renamed/split: run `uv run serena memories check` from the project root to find broken `mem:` references.
|
||||
If memories were edited/renamed/split: run `uv run --no-sync serena memories check` from the project root to find broken `mem:` references.
|
||||
@@ -1,10 +0,0 @@
|
||||
# Tech Stack
|
||||
|
||||
- Language: Python 3.11–3.14 (`requires-python = ">=3.11, <3.15"`).
|
||||
- Package/dep manager: **uv** (uv.lock present). Exact version pins in `pyproject.toml` because `uvx` installs from git and ignores the lockfile.
|
||||
- Build backend: `hatchling`. Packages: `src/serena`, `src/interprompt`, `src/solidlsp`.
|
||||
- Task runner: **poethepoet** (`poe <task>`). Poe executor is `simple` (does NOT shell out via uv) — avoids env recreation while MCP server is running.
|
||||
- Key runtime deps: `mcp`, `flask` (dashboard), `pydantic`, `pygls` + `lsprotocol` (LSP), `anthropic`, `jinja2`, `ruamel.yaml`, `pywebview`/`pystray` (GUI), `pythonnet` (Windows only).
|
||||
- Dev deps: `ruff` (lint+format), `ty` (Astral type checker; replaced mypy), `pytest` (+ `pytest-xdist`, `pytest-timeout`, `syrupy` snapshots), `sphinx`/`jupyter-book` for docs.
|
||||
- Optional extras: `agno` (Agno agent integration), `google` (gemini).
|
||||
- LSP client core lives under `src/solidlsp/`; one subdir per supported language server under `language_servers/`.
|
||||
+4
-30
@@ -68,37 +68,11 @@ excluded_tools: []
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
included_optional_tools: []
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
# initial prompt for the project, which will be provided to the LLM upon project activation
|
||||
# (or within Serena's initial instructions if the project is activated at startup).
|
||||
## See: https://oraios.github.io/serena/02-usage/050_configuration.html#prompt-templates
|
||||
initial_prompt: |
|
||||
IMPORTANT: You use an idiomatic, object-oriented style (Java-esque principles, Pythonic syntax and constructs).
|
||||
|
||||
Design:
|
||||
* You keep each concern in exactly one home.
|
||||
A mechanism whose parts are only correct in combination is implemented as a single component/class, and its parts are private to it;
|
||||
expose a minimal public surface. Java-like encapsulation: If helper functions and constants are only used by one abstraction,
|
||||
make them internal to the respective class.
|
||||
* You enforce invariants through structure and visibility; interfaces should not permit states the design forbids.
|
||||
* For any non-trivial interfaces, you use interfaces that expect explicitly typed abstractions
|
||||
rather than mere functions (i.e. use the strategy pattern, for example).
|
||||
You avoid the use of low-level data structures in all cases where an object-oriented abstraction would be more appropriate.
|
||||
For simple data storage, you use dataclasses instead of dictionaries or tuples.
|
||||
|
||||
Testing:
|
||||
You test externally observable behavior and guarantees, never implementation structure.
|
||||
|
||||
Docstrings & Comments:
|
||||
* You consistently use reStructuredText.
|
||||
* You structure function implementations into functional blocks that are separated by blank lines.
|
||||
Atop each functional block, you write an elliptical phrase (starting with lower-case letter) that describes the purpose of the
|
||||
block in a concise manner.
|
||||
* When describing parameters, methods/functions and classes, you use a precise style, where the initial (elliptical) phrase
|
||||
clearly defines *what* it is. Any details then follow in subsequent sentences.
|
||||
* Each piece of information appears exactly once, at the element that owns it: callers do not
|
||||
explain callees' internals, and callees do not describe their callers.
|
||||
|
||||
Pull requests:
|
||||
Read `mem:creating_pull_requests` when asked to participate in the creation of a pull request.
|
||||
{{ embed_memory("critical_info") }}
|
||||
|
||||
# the encoding used by text files in the project
|
||||
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||
|
||||
+39
-3
@@ -3,7 +3,7 @@
|
||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||
|
||||
* General:
|
||||
- Fix: the README, the Language Support docs page and the project template omitted several already-supported language servers
|
||||
- Fix: Race conditions in ProjectServer when used by multiple clients in parallel
|
||||
- Fix: a tool call exceeding the timeout blocked the task executor indefinitely; the executor now
|
||||
recovers without user-induced cancellation
|
||||
- Add Grok Build support (context `grok`, setup CLI, hooks)
|
||||
@@ -13,6 +13,12 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
- Project activation errors are now reported to the client in Serena's system prompt, instead of failures
|
||||
being visible only in the log. This applies both to a failed activation of an explicitly given project and to a
|
||||
failed `--project-from-cwd` auto-detection (#1773).
|
||||
- ProjectServer: Configure trusted hosts (local hosts only) when listening on localhost
|
||||
- SerenaDashboardTrayManager: Configure trusted hosts (local hosts only)
|
||||
- Enclose sub-prompts in XML-like tags to make scopes explicit
|
||||
- Prompts and prompt templates:
|
||||
- Allow initial project prompts and project-specific newly activated modes to use templating
|
||||
- Support function `embed_memory` in prompt templates to inline a memory's contents
|
||||
- Security: Use sandboxed environment for prompt templating, preventing attackers from using custom prompts to
|
||||
execute commands in an uncontrolled manner
|
||||
|
||||
@@ -36,9 +42,17 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
Note that if the base path is itself ignored, ignored paths cannot be considered.
|
||||
|
||||
* Language Servers:
|
||||
- Add Gleam language server support (via the `gleam lsp` server bundled with the Gleam compiler)
|
||||
- Allow language server priorities to be configured in `serena_config.yml` (for auto-detection during
|
||||
project creation)
|
||||
- **Add support for Nextflow** (language server `nextflow`), using the official
|
||||
[Nextflow language server](https://github.com/nextflow-io/language-server); the JAR is downloaded
|
||||
automatically, a Java 17+ runtime is required
|
||||
- Add `python_basedpyright` as an alternative Python language server
|
||||
- Java/JDTLS: stop downloading and loading the unused IntelliCode completion-ranking bundle; the retired
|
||||
`intellicode_version`, `intellicode_xmx` and `intellicode_xms` settings remain accepted but are ignored #1821
|
||||
- Kotlin: update the managed Kotlin LSP from `261.13587.0` to `262.9593.0`, including support for the
|
||||
new platform-specific archive layout and Windows ARM64 builds
|
||||
- Nix/nixd: support custom `ls_path` launchers and external JSON settings through `config_path` #1737
|
||||
- Fix: Nix/nixd diagnostics now use published diagnostics instead of the unsupported
|
||||
`textDocument/diagnostic` request, which terminated nixd #1802
|
||||
@@ -65,6 +79,11 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
or `textDocument/didChange`), as this is always the intention of calling the method.
|
||||
If files were kept open in the language server (which the Svelte and Vue language servers did),
|
||||
the language server was not necessarily informed about updated contents.
|
||||
- Add Deno support (experimental; language server `deno`, backed by the Deno CLI's built-in
|
||||
`deno lsp`). Understands Deno module resolution (`npm:` / `jsr:` / `https:` imports) and the
|
||||
`Deno.*` globals, which the plain TypeScript language server does not. Overlaps TypeScript on
|
||||
file extensions, so it is not auto-detected and must be selected explicitly; requires the
|
||||
`deno` CLI on PATH
|
||||
|
||||
* JetBrains:
|
||||
- `jet_brains_find_symbol`: Disallow wildcard-only search, delegating to overview tool if request is for file
|
||||
@@ -83,11 +102,28 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
- Language servers and their dependency providers now go through the `subprocess_run` helper instead of
|
||||
calling `subprocess.run` directly (e.g. for installation processes), so all such subprocesses get
|
||||
`stdin=DEVNULL` and can no longer interfere with the stdio MCP connection #1748
|
||||
- `scala`: Fix: Metals asks via `window/showMessageRequest` whether to import a workspace it has not
|
||||
seen before, and Serena had no handler, so the request failed with `MethodNotFound` and Metals gave
|
||||
up on the import ("Unexpected error initializing server"). No build server was ever connected and
|
||||
every cross-file query fell back to the presentation compiler, which sees one file at a time, unless
|
||||
the project happened to have been imported beforehand by another editor. The three prompts that lead
|
||||
to a build server are now answered; anything else is dismissed, including the choice between several
|
||||
build definitions in one workspace. `ls_specific_settings.scala.auto_import_build: false` opts out
|
||||
- `scala`: Fix: in a repository whose builds live below its root, Metals was given only the repository
|
||||
root as a workspace folder, and its own one-level search takes just the first build it finds — so in
|
||||
a monorepo all but one build were served with no build target, silently returning no cross-file
|
||||
references. The build roots are now detected and passed as workspace folders, one Metals service per
|
||||
build; `ls_specific_settings.scala.project_roots` and `project_root_scan_depth` override the
|
||||
detection #1766
|
||||
|
||||
* Dashboard:
|
||||
- Fix: Serena PyPI version check triggered by callback on main thread could delay agent startup #1774
|
||||
- Fix: on macOS, the `tray_manager` interface put an icon in the Dock and in the app switcher
|
||||
(should only use menu bar icon)
|
||||
- Improvements in `tray_manager` interface mode:
|
||||
- Fix: on macOS, the `tray_manager` interface put an icon in the Dock and in the app switcher
|
||||
(should only use menu bar icon)
|
||||
- On Serena shutdown, message the tray manager before lengthy project shutdowns
|
||||
- Fix: When dead ports are detected (Serena instance gone), explicitly update the tray menu
|
||||
immediately (may not update automatically)
|
||||
- Use `tray_manager` interface as new default on macOS
|
||||
|
||||
* Hooks:
|
||||
|
||||
@@ -110,7 +110,7 @@ Serena incorporates a powerful abstraction layer for the integration of language
|
||||
The underlying language servers are typically open-source projects or at least freely available for use.
|
||||
|
||||
When using Serena's language server backend, we provide **support for over 40 programming languages**, including
|
||||
Ada / SPARK, AL, Angular, Ansible, Bash, BSL, C#, C/C++, Clojure, Crystal, CUE, Dart, Elixir, Elm, Erlang, Fortran, F#, GDScript, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, LaTeX, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Pascal, Perl, PHP, PowerShell, Python, QML, R, Rego, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Svelte, Swift, SystemVerilog, Terraform, TOML, TypeScript, Vue, WGSL, YAML, and Zig.
|
||||
Ada / SPARK, AL, Angular, Ansible, Bash, BSL, C#, C/C++, Clojure, Crystal, CUE, Dart, Deno, Elixir, Elm, Erlang, Fortran, F#, GDScript, Gleam, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, LaTeX, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nextflow, Nix, OCaml, Pascal, Perl, PHP, PowerShell, Python, QML, R, Rego, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Svelte, Swift, SystemVerilog, Terraform, TOML, TypeScript, Vue, WGSL, YAML, and Zig.
|
||||
|
||||
### The Serena JetBrains Plugin
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ Some languages require additional installations or setup steps, as noted.
|
||||
(requires the Godot editor to be running with its built-in LSP enabled — default on port 6008;
|
||||
Serena connects over TCP and does not launch Godot itself;
|
||||
see the [GDScript Setup Guide](../03-special-guides/godot_gdscript_setup_guide_for_serena) for details)
|
||||
* **Gleam**
|
||||
(requires the [Gleam compiler](https://gleam.run) on PATH; the language server is bundled with the compiler and started via `gleam lsp`)
|
||||
* **Go**
|
||||
(requires installation of `gopls`)
|
||||
* **Groovy**
|
||||
@@ -115,6 +117,11 @@ Some languages require additional installations or setup steps, as noted.
|
||||
* **mSL** (mIRC Scripting Language)
|
||||
(auto-installed; no external dependencies required — uses a custom pygls-based LSP server shipped with Serena;
|
||||
supports document symbols, workspace symbols, references, and go-to-definition for aliases, events, menus, dialogs, and CTCP handlers in `.mrc` files)
|
||||
* **Nextflow**
|
||||
(uses the official [Nextflow language server](https://github.com/nextflow-io/language-server), which is automatically
|
||||
downloaded; requires a Java 17+ runtime, discovered via `ls_specific_settings.nextflow.java_home`, `JAVA_HOME` or `java` on PATH;
|
||||
covers `.nf` scripts — Nextflow `.config` files are not treated as source files, since the language server reports no symbols for them;
|
||||
processes, workflows and functions are reported under their declared name, e.g. `GREET` for `process GREET`)
|
||||
* **Nix**
|
||||
(requires nixd installation)
|
||||
* **OCaml**
|
||||
@@ -147,7 +154,7 @@ Some languages require additional installations or setup steps, as noted.
|
||||
* **Rust**
|
||||
(requires [rustup](https://rustup.rs/) - uses rust-analyzer from your toolchain)
|
||||
* **Scala**
|
||||
(requires some [manual setup](../03-special-guides/scala_setup_guide_for_serena); uses Metals LSP)
|
||||
(uses Metals LSP, which imports the build on first use — see the [setup guide](../03-special-guides/scala_setup_guide_for_serena))
|
||||
* **SCSS / Sass / CSS**
|
||||
(experimental; requires Node.js + npm; uses [some-sass-language-server](https://github.com/wkillerud/some-sass) to handle
|
||||
`.scss`, `.sass`, and `.css`)
|
||||
@@ -164,6 +171,12 @@ Some languages require additional installations or setup steps, as noted.
|
||||
* **TOML**
|
||||
(experimental; uses Taplo 0.10.0, taken from PATH if present, otherwise downloaded automatically)
|
||||
* **TypeScript**
|
||||
* **Deno**
|
||||
(experimental; requires the `deno` CLI on PATH — it bundles the language server used here;
|
||||
serves Deno TypeScript/JavaScript and understands `npm:` / `jsr:` / `https:` imports and the `Deno.*`
|
||||
globals, which the plain TypeScript language server does not; overlaps `typescript` on file extensions,
|
||||
so it is not auto-detected and must be set as the language explicitly — do not also enable `typescript`
|
||||
for the same files)
|
||||
* **Vue**
|
||||
(3.x with TypeScript; requires Node.js v18+ and npm; supports .vue Single File Components with monorepo detection)
|
||||
* **YAML**
|
||||
|
||||
@@ -155,6 +155,43 @@ You can manage modes using the `mode` command,
|
||||
serena mode edit <mode-name>
|
||||
serena mode delete <mode-name>
|
||||
|
||||
(prompt-templates)=
|
||||
## Prompt Templates
|
||||
|
||||
All prompts that Serena provides to the LLM are [Jinja2](https://jinja.palletsprojects.com/) templates.
|
||||
Templating applies to
|
||||
|
||||
* **Serena's system prompt** (the "Serena Instructions Manual"), which is defined in the prompt template `system_prompt`
|
||||
(see [Custom Prompts](custom-prompts) for how to override it),
|
||||
* **context and mode prompts**, i.e. the `prompt` field in context and mode definition files, and
|
||||
* **the project prompt**, i.e. `initial_prompt` in `project.yml`, which is provided to the LLM upon project activation.
|
||||
|
||||
Templating allows prompts to adapt to the active configuration; for instance, a mode prompt can mention a tool
|
||||
only if that tool is actually available in the current session.
|
||||
|
||||
### Variables and Functions
|
||||
|
||||
The following variables can be used in all of the above templates:
|
||||
|
||||
| Variable | Description |
|
||||
|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `available_tools` | the list of names of the tools that are currently exposed to the LLM. Use it to include content conditionally, e.g. `{% if 'replace_content' in available_tools %}…{% endif %}`. |
|
||||
| `available_markers` | the list of names of the tool markers (tool categories, e.g. `ToolMarkerSymbolicRead`) for which at least one tool is exposed; useful for conditioning on entire groups of tools. |
|
||||
| `tool_names` | a mapping from canonical tool names to effective tool names, which accounts for legacy tool renames as well as for tools being functionally replaced due to the active language backend (e.g. `find_symbol` being replaced by `jet_brains_find_symbol` when the JetBrains backend is active). Prefer `{{ tool_names['find_symbol'] }}` over hard-coded names. |
|
||||
|
||||
Context, mode and project prompts (but not Serena's system prompt) additionally support the following function:
|
||||
|
||||
| Function | Description |
|
||||
|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `embed_memory(name)` | embeds the content of the memory with the given name, wrapped in a tag `<memory name="...">`. Use this to inline knowledge that shall always be provided to the LLM rather than being loaded on demand. If the memory cannot be loaded, an error is logged and nothing is rendered. |
|
||||
|
||||
For example, a project can inline its coding conventions from a memory into the project prompt:
|
||||
|
||||
```yaml
|
||||
initial_prompt: |
|
||||
{{ embed_memory("coding_conventions") }}
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
For advanced users, Serena's configuration can be further customized.
|
||||
@@ -666,8 +703,7 @@ Supported settings:
|
||||
Java support has two installation modes:
|
||||
|
||||
1. **Default vscode-java VSIX mode** (no extra config required): Serena downloads the platform-specific
|
||||
vscode-java VSIX (~500 MB: JDTLS + bundled JRE 21 + Lombok + IntelliCode), Gradle distribution and
|
||||
IntelliCode VSIX from public hosts on first use.
|
||||
vscode-java VSIX (JDTLS + bundled JRE 21 + Lombok) and a Gradle distribution from public hosts on first use.
|
||||
2. **Upstream JDTLS mode** (offline-friendly): Activated by setting both `jdtls_path` and `lombok_path`.
|
||||
Uses an existing JDTLS installation (~100 MB) and the system JDK 21+. Nothing is downloaded.
|
||||
Recommended for restricted-network/corporate environments.
|
||||
@@ -677,9 +713,8 @@ Java support has two installation modes:
|
||||
- **Default vscode-java VSIX mode** — recommended for most users. No setup required;
|
||||
Serena downloads everything on first use.
|
||||
- **Upstream JDTLS mode** — recommended when:
|
||||
- you cannot reach `github.com`, `services.gradle.org` or `marketplace.visualstudio.com`
|
||||
from the host (corporate proxy, air-gapped network);
|
||||
- you want a smaller on-disk footprint (~100 MB vs ~500 MB);
|
||||
- you cannot reach `github.com` or `services.gradle.org` from the host (corporate proxy, air-gapped network);
|
||||
- you want a smaller on-disk footprint;
|
||||
- you already maintain a JDTLS installation (e.g. for `nvim-jdtls` or another editor);
|
||||
- your security policy prohibits per-project runtime downloads.
|
||||
|
||||
@@ -702,23 +737,19 @@ The following settings are supported for the Java language server:
|
||||
| `runtimes` | `[]` | Extra JRE/JDK entries registered with JDT-LS via `java.configuration.runtimes`. Use this when a project's source/target level exceeds the JDK JDT-LS itself runs on (currently JDK 21 in default vscode-java VSIX mode). Each entry is a mapping with required `name` (e.g. `JavaSE-25`, matching the `JavaSE-NN` container the build tool requests) and `path` (JDK/JRE home directory; must exist), plus optional `default`, `sources`, and `javadoc` (passed through to JDT-LS). Entries extend rather than replace the bundled `JavaSE-21` runtime; an entry that reuses the `JavaSE-21` name overrides the bundled one. Changing this setting invalidates the JDTLS workspace hash so a fresh import is performed. |
|
||||
| `gradle_version` | `8.14.2` | (vscode-java mode only) Override the Gradle distribution version Serena downloads by default. |
|
||||
| `vscode_java_version` | `1.54.0-923` | (vscode-java mode only) Override the bundled `vscode-java` runtime bundle version Serena downloads by default. |
|
||||
| `intellicode_version` | `1.2.30` | (vscode-java mode only) Override the IntelliCode VSIX version Serena downloads by default. |
|
||||
| `lombok_show_generated` | `true` | Show Lombok-generated methods (`getX/setX`, `builder()`, `equals/hashCode/toString`, `withX`, fluent accessors) in `find_symbol`, `get_symbols_overview` and the symbol-edit tools. Set to `false` to restore the previous JDTLS default and hide the synthetic methods (e.g. when `@Data` classes pollute the outline with too many getters/setters). Requires JDTLS commit `b2d8952` / `vscode-java >= 1.53.0`; the bundled default already meets this. |
|
||||
| `jdtls_xmx` | `3G` | Maximum heap size for the JDTLS server JVM. |
|
||||
| `jdtls_xms` | `100m` | Initial heap size for the JDTLS server JVM. |
|
||||
| `intellicode_xmx` | `1G` | (vscode-java mode only) Maximum heap size for the IntelliCode embedded JVM. |
|
||||
| `intellicode_xms` | `100m` | (vscode-java mode only) Initial heap size for the IntelliCode embedded JVM. |
|
||||
|
||||
Notes:
|
||||
- When overriding `vscode_java_version`, Serena still assumes that the downloaded runtime bundle keeps the same internal
|
||||
directory layout and file names as the bundled default version.
|
||||
- In upstream-jdtls mode, IntelliCode is not loaded (it's an ML completions ranker that is irrelevant to Serena's
|
||||
symbol-tools workflow), and Serena does not ship a Gradle distribution. Maven projects work via JDTLS's bundled m2e.
|
||||
- Serena does not download or load IntelliCode because its completion ranking is not used by Serena's tools. The retired
|
||||
`intellicode_version`, `intellicode_xmx` and `intellicode_xms` keys remain accepted and ignored so existing
|
||||
configurations continue to load; they can be removed.
|
||||
- In upstream-jdtls mode Serena does not ship a Gradle distribution. Maven projects work via JDTLS's bundled m2e.
|
||||
Gradle projects must have `./gradlew` in the project, or rely on a system-installed Gradle through Buildship's
|
||||
default discovery rules.
|
||||
- In upstream-jdtls mode the `gradle_version`, `vscode_java_version`, `intellicode_version`,
|
||||
`intellicode_xmx`, `intellicode_xms` settings are silently ignored — they only apply to the
|
||||
vscode-java VSIX mode.
|
||||
default discovery rules. The `gradle_version` and `vscode_java_version` settings are silently ignored in this mode.
|
||||
- Without `runtimes`, JDT-LS only knows about the bundled `JavaSE-21` JRE. Projects that request a newer
|
||||
container (e.g. `sourceCompatibility = JavaVersion.VERSION_25`) then fail to resolve JDK types such as
|
||||
`java.lang.Object`. Register the matching installed JDK via `runtimes` instead of symlinking over Serena's
|
||||
@@ -765,15 +796,20 @@ Supported settings:
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed download | Override the Kotlin Language Server executable path. |
|
||||
| `kotlin_lsp_version` | `261.13587.0` | Override the Kotlin Language Server version Serena downloads when `ls_path` is not set. |
|
||||
| `kotlin_lsp_version` | `262.9593.0` | Override the Kotlin Language Server version Serena downloads when `ls_path` is not set. |
|
||||
| `jvm_options` | `-Xmx2G` | Value assigned to `JAVA_TOOL_OPTIONS` for the Kotlin LS process. Set to `""` to disable JVM options entirely. |
|
||||
|
||||
The managed `262.9593.0` packages include a bundled JBR. For a custom `ls_path`, point directly to
|
||||
`bin/intellij-server` (`bin/intellij-server.exe` on Windows). Serena also retains the legacy download
|
||||
layout for custom Kotlin LSP versions older than `262.4739.0`. The pinned current and frozen initial
|
||||
releases are checksum-verified; arbitrary custom versions are downloaded without checksum verification.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
kotlin:
|
||||
kotlin_lsp_version: "261.13587.0"
|
||||
kotlin_lsp_version: "262.9593.0"
|
||||
jvm_options: "-Xmx4G -XX:+UseG1GC"
|
||||
```
|
||||
|
||||
@@ -1248,6 +1284,7 @@ Supported settings:
|
||||
| `yaml_language_server_version` | `1.19.2` | Override the npm package version Serena installs when `ls_path` is not set. |
|
||||
| `npm_registry` | `null` | Override the npm registry Serena uses for the managed install. |
|
||||
|
||||
(custom-prompts)=
|
||||
### Custom Prompts
|
||||
|
||||
All of Serena's prompts can be fully customized.
|
||||
|
||||
@@ -111,7 +111,9 @@ By default, these services accept connections from localhost only, which is a se
|
||||
|
||||
These services can be reconfigured to listen on other addresses, but doing so may have security implications.
|
||||
If you need to allow connections from other machines, we recommend that you set up a secure networking environment
|
||||
(e.g. using a VPN or SSH tunnels) and ensure that only trusted machines can connect to these services.
|
||||
and ensure that only trusted machines can connect to these services.
|
||||
It is the responsibility of the user to restrict access appropriately, e.g. by placing the service behind a reverse
|
||||
proxy (adding authentication) or firewall.
|
||||
|
||||
## Supply Chain Security
|
||||
|
||||
|
||||
@@ -15,7 +15,16 @@ Install the following on your system and ensure they are available on `PATH`:
|
||||
- Serena uses `cs` if available; if only `coursier` exists, it will attempt to install `cs`. If neither is present, install Coursier first.
|
||||
|
||||
---
|
||||
## Quick Start (Recommended: VS Code + Metals auto‑import)
|
||||
## Quick Start
|
||||
|
||||
Start Serena in your project root. Metals asks whether to import a workspace it has not seen before, and Serena answers that prompt for it — the build is imported (for sbt, by running `sbt bloopInstall`), `.bloop/` and `.metals/` are created, and cross-file navigation works from there. The first run therefore takes as long as your build takes to load.
|
||||
|
||||
Set `auto_import_build: false` under `ls_specific_settings.scala` to decline instead; you then need to import the build yourself by one of the routes below, or cross-file queries will be served by the fallback presentation compiler and see only one file at a time.
|
||||
|
||||
Serena answers three of Metals' prompts — “Import build”, “Import changes”, “Connect”. Anything else Metals asks is dismissed and logged, including “Multiple build definitions found. Which would you like to use?”, so a workspace holding more than one kind of build (say both an sbt and a Maven definition) still needs importing by one of the routes below.
|
||||
|
||||
---
|
||||
## Importing the build yourself (VS Code)
|
||||
|
||||
1. Open your Scala project in VS Code.
|
||||
2. When prompted by Metals, accept “Import build”. Wait until the import and initial compile/indexing finish.
|
||||
@@ -25,7 +34,7 @@ Install the following on your system and ensure they are available on `PATH`:
|
||||
This flow ensures the `.bloop/` and (if applicable) `.metals/` directories are created and your build is known to the build server that Metals uses.
|
||||
|
||||
---
|
||||
## Manual Setup (No VS Code)
|
||||
## Importing the build yourself (No VS Code)
|
||||
|
||||
Follow these steps if you prefer a manual setup or you are not using VS Code:
|
||||
|
||||
@@ -63,6 +72,21 @@ These instructions cover the setup for projects that use sbt as the build tool,
|
||||
Notes:
|
||||
- Ensure you completed the manual or auto‑import steps so that the build is compiled and indexed; otherwise, code navigation and references may be incomplete until the first successful compile.
|
||||
|
||||
---
|
||||
## Monorepos: builds below the repository root
|
||||
|
||||
Metals serves one build per workspace folder, so what it needs is the build roots, not the repository root. Serena detects them: if the repository root is not itself a build root (no `build.sbt`, `build.mill`, `pom.xml`, `.bsp/`, …), it searches up to three levels below for directories that are, and passes those to Metals — one Metals service per build.
|
||||
|
||||
Override the detection where it guesses wrong:
|
||||
|
||||
```yaml
|
||||
# ~/.serena/serena_config.yml or .serena/project.yml
|
||||
ls_specific_settings:
|
||||
scala:
|
||||
project_roots: ["backend", "tooling/plugin"] # relative to the repository root
|
||||
project_root_scan_depth: 3 # only applies when project_roots is unset
|
||||
```
|
||||
|
||||
---
|
||||
## Running Multiple Metals Instances
|
||||
|
||||
|
||||
+5
-2
@@ -48,7 +48,7 @@ dependencies = [
|
||||
"starlette==1.3.1",
|
||||
"python-multipart==0.0.31",
|
||||
"filelock==3.25.2",
|
||||
"cryptography==48.0.1",
|
||||
"cryptography==50.0.0",
|
||||
"regex==2026.2.28",
|
||||
"pythonnet==3.1.0 ; sys_platform == 'win32'",
|
||||
"oslex==2.0.0",
|
||||
@@ -94,7 +94,7 @@ dev = [
|
||||
# Exact pins because uvx installs from git, ignoring the lock file.
|
||||
"tornado==6.5.7",
|
||||
"wheel==0.46.3",
|
||||
"pyasn1==0.6.3",
|
||||
"pyasn1==0.6.4",
|
||||
"PyJWT==2.12.0",
|
||||
]
|
||||
agno = ["agno==2.6.6", "sqlalchemy==2.0.41"] # agno bumped for session state overwrite CVE
|
||||
@@ -328,6 +328,7 @@ markers = [
|
||||
"groovy: language server running for Groovy",
|
||||
"rust: language server running for Rust",
|
||||
"typescript: language server running for TypeScript",
|
||||
"deno: language server running for Deno (uses the built-in deno lsp)",
|
||||
"svelte: language server running for Svelte (uses svelte-language-server)",
|
||||
"vue: language server running for Vue (uses TypeScript LSP)",
|
||||
"php: language server running for PHP",
|
||||
@@ -344,6 +345,7 @@ markers = [
|
||||
"zig: language server running for Zig",
|
||||
"lua: language server running for Lua",
|
||||
"luau: language server running for Luau",
|
||||
"nextflow: language server running for Nextflow",
|
||||
"nix: language server running for Nix",
|
||||
"dart: language server running for Dart",
|
||||
"erlang: language server running for Erlang",
|
||||
@@ -378,6 +380,7 @@ markers = [
|
||||
"angular: language server running for Angular (uses @angular/language-server + @angular/language-service tsserver plugin)",
|
||||
"ada: language server running for Ada / SPARK (uses AdaCore Ada Language Server)",
|
||||
"qml: language server running for QML (uses Qt's qmlls)",
|
||||
"gleam: language server running for Gleam (uses the Gleam compiler's bundled `gleam lsp`)",
|
||||
]
|
||||
|
||||
[tool.codespell]
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from sensai.util import logging
|
||||
|
||||
from solidlsp.language_servers.eclipse_jdtls import EclipseJDTLS
|
||||
from solidlsp.language_servers.kotlin_language_server import KotlinLanguageServer
|
||||
from solidlsp.language_servers.nextflow_language_server import NextflowLanguageServer
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.configure()
|
||||
EclipseJDTLS.DependencyProvider.update_dep_hashes()
|
||||
NextflowLanguageServer.DependencyProvider.update_dep_hashes()
|
||||
KotlinLanguageServer.DependencyProvider.update_dep_hashes()
|
||||
+67
-25
@@ -956,14 +956,43 @@ class SerenaAgent:
|
||||
result[tool_class.get_name_from_cls()] = new_tool_class.get_name_from_cls()
|
||||
return result
|
||||
|
||||
def _format_prompt(self, prompt_template: str) -> str:
|
||||
@staticmethod
|
||||
def _format_prompt_tag(text: str, tag: str, tag_name_attr: str | None = None) -> str:
|
||||
open_tag = f"<{tag}" + (f' name="{tag_name_attr}"' if tag_name_attr is not None else "") + ">"
|
||||
close_tag = f"</{tag}>"
|
||||
return f"{open_tag}\n{text.strip()}\n{close_tag}"
|
||||
|
||||
def _render_prompt(self, prompt_template: str, tag: str | None = None, tag_name_attr: str | None = None) -> str:
|
||||
"""
|
||||
Renders the given prompt template, providing the necessary variables and functions
|
||||
|
||||
:param prompt_template: the template text (jinja2) to render
|
||||
:param tag: if not None, wraps the rendered prompt text in a tag
|
||||
:param tag_name_attr: for the case where tag is not None, specifies the value of the "name" attribute of the tag
|
||||
:return: the rendered prompt
|
||||
"""
|
||||
|
||||
def embed_memory(memory_name: str) -> str:
|
||||
try:
|
||||
memory_manager = self._get_memory_manager()
|
||||
return self._format_prompt_tag(memory_manager.load_memory(memory_name), tag="memory", tag_name_attr=memory_name)
|
||||
except Exception as e:
|
||||
log.error("Tried to embed memory '%s' but failed to load it: %s", memory_name, e)
|
||||
return ""
|
||||
|
||||
template = JinjaTemplate(prompt_template)
|
||||
return template.render(
|
||||
text = template.render(
|
||||
available_tools=self._exposed_tools.tool_names,
|
||||
available_markers=self._exposed_tools.tool_marker_names,
|
||||
tool_names=self._prompt_tool_names_mapping,
|
||||
embed_memory=embed_memory,
|
||||
)
|
||||
|
||||
if tag is not None:
|
||||
text = self._format_prompt_tag(text, tag=tag, tag_name_attr=tag_name_attr)
|
||||
|
||||
return text
|
||||
|
||||
def create_connection_prompt(self) -> str:
|
||||
"""
|
||||
Returns the bootstrap prompt to be sent at MCP connection time.
|
||||
@@ -972,6 +1001,21 @@ class SerenaAgent:
|
||||
"""
|
||||
return self.prompt_factory.create_connection_prompt()
|
||||
|
||||
def _create_global_memory_manager(self) -> MemoryManager:
|
||||
"""
|
||||
:return: a memory manager for global memories only (no project memories)
|
||||
"""
|
||||
return MemoryManager(serena_data_folder=None, read_only_memory_patterns=self.serena_config.read_only_memory_patterns)
|
||||
|
||||
def _get_memory_manager(self) -> MemoryManager:
|
||||
"""
|
||||
:return: the memory manager for the active project (if any) or a global memory manager if no project is active
|
||||
"""
|
||||
if self._active_project is not None:
|
||||
return self._active_project.memory_manager
|
||||
else:
|
||||
return self._create_global_memory_manager()
|
||||
|
||||
def create_system_prompt(self, session_id: str = "global") -> str:
|
||||
"""
|
||||
Returns the 'Serena Instructions Manual', i.e. Serena's system prompt.
|
||||
@@ -981,9 +1025,7 @@ class SerenaAgent:
|
||||
"""
|
||||
available_tools = self._active_tools
|
||||
available_markers = available_tools.tool_marker_names
|
||||
global_memories = MemoryManager(
|
||||
serena_data_folder=None, read_only_memory_patterns=self.serena_config.read_only_memory_patterns
|
||||
).list_global_memories()
|
||||
global_memories = self._create_global_memory_manager().list_global_memories()
|
||||
global_memories_str = dict_string(global_memories.to_dict()) if len(global_memories) > 0 else ""
|
||||
log.info("Generating system prompt with available_tools=(see active tools), available_markers=%s", available_markers)
|
||||
|
||||
@@ -997,8 +1039,8 @@ class SerenaAgent:
|
||||
self._project_prompt_status.mark_mode_prompts_as_provided(session_id)
|
||||
|
||||
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 relevant_modes],
|
||||
context_system_prompt=self._render_prompt(self._context.prompt, tag="context"),
|
||||
mode_system_prompts=[self._render_prompt(mode.prompt, tag="mode", tag_name_attr=mode.name) for mode in relevant_modes],
|
||||
available_tools=available_tools.tool_names,
|
||||
available_markers=available_markers,
|
||||
global_memories_list=global_memories_str,
|
||||
@@ -1007,11 +1049,11 @@ class SerenaAgent:
|
||||
|
||||
# provide the project activation message if it hasn't yet been provided
|
||||
if self._active_project is not None and not self._project_prompt_status.is_project_activation_message_already_provided(session_id):
|
||||
system_prompt += "\n\n" + self.get_project_activation_message(session_id)
|
||||
system_prompt += "\n\n" + self._format_prompt_tag(self.get_project_activation_message(session_id), tag="active-project")
|
||||
elif self._project_activation_error:
|
||||
system_prompt += f"\n\nNo project is active ({self._project_activation_error})."
|
||||
|
||||
return system_prompt
|
||||
return self._format_prompt_tag(system_prompt, tag="serena")
|
||||
|
||||
def get_project_activation_message(self, session_id: str) -> str:
|
||||
"""
|
||||
@@ -1031,13 +1073,13 @@ class SerenaAgent:
|
||||
|
||||
# provide basic project information (name, location, languages, encoding)
|
||||
if proj.is_newly_created:
|
||||
msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}. "
|
||||
msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}.\n"
|
||||
else:
|
||||
msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated."
|
||||
msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated.\n"
|
||||
if self._language_backend == LanguageBackend.LSP:
|
||||
languages_str = ", ".join([lang.value for lang in proj.project_config.language_servers])
|
||||
msg += f"\nProgramming languages: {languages_str}."
|
||||
msg += f"File encoding: {proj.project_config.encoding}."
|
||||
language_servers_str = ", ".join([ls.value for ls in proj.project_config.language_servers])
|
||||
msg += f"Active language servers: {language_servers_str}.\n"
|
||||
msg += f"File encoding: {proj.project_config.encoding}.\n"
|
||||
|
||||
# add list of memories (if memories are enabled)
|
||||
include_memories = self._active_tools.contains_tool_class(ReadMemoryTool)
|
||||
@@ -1045,27 +1087,26 @@ class SerenaAgent:
|
||||
project_memories = proj.memory_manager.list_project_memories()
|
||||
if project_memories:
|
||||
msg += (
|
||||
f"\n{json.dumps(project_memories.to_dict())}\n"
|
||||
+ "Use the `read_memory` tool to read these memories later if they are relevant to the task."
|
||||
f"{json.dumps(project_memories.to_dict())}\n"
|
||||
+ f"Use the `{ReadMemoryTool.get_name_from_cls()}` tool to read these memories later if they are relevant to the task.\n"
|
||||
)
|
||||
elif self._active_tools.contains_tool_class(OnboardingTool):
|
||||
msg += "Onboarding has not been performed yet, you should call Serena's `onboarding` tool now to set up project memories."
|
||||
msg += f"Onboarding has not been performed yet. Ask the user whether to perform onboarding via the `{OnboardingTool.get_name_from_cls()}` tool.\n"
|
||||
|
||||
# add prompts for modes that were dynamically activated by the project
|
||||
modes_with_prompts = self._project_prompt_status.get_modes_with_prompts_to_be_provided_for_project_activation(session_id)
|
||||
if modes_with_prompts:
|
||||
msg += "\nNewly applicable mode instructions:"
|
||||
for mode in modes_with_prompts:
|
||||
msg += f"\n{mode.prompt}"
|
||||
msg += self._render_prompt(mode.prompt, tag="mode", tag_name_attr=mode.name) + "\n"
|
||||
self._project_prompt_status.mark_mode_prompts_as_provided(session_id)
|
||||
|
||||
# add project-specific prompt
|
||||
if proj.project_config.initial_prompt:
|
||||
msg += f"\nProject-specific instructions:\n {proj.project_config.initial_prompt}"
|
||||
msg += "\n" + self._render_prompt(proj.project_config.initial_prompt, tag="project-instructions")
|
||||
|
||||
self._project_prompt_status.mark_project_activation_message_as_provided(session_id)
|
||||
|
||||
return self._format_prompt(msg)
|
||||
return msg
|
||||
|
||||
def _update_active_modes(self, log_message: bool = True) -> None:
|
||||
"""
|
||||
@@ -1431,10 +1472,7 @@ class SerenaAgent:
|
||||
Shutdown handler of the agent, freeing resources and stopping background tasks.
|
||||
"""
|
||||
log.info("SerenaAgent is shutting down ...")
|
||||
if self._active_project is not None:
|
||||
log.info(f"Shutting down active project '{self._active_project.project_name}' ...")
|
||||
self._active_project.shutdown(timeout=timeout)
|
||||
self._active_project = None
|
||||
# apply shutdown depending on allocated resources, handling quick ones first (dashboard manager & GUI viewer)
|
||||
if self._gui_log_viewer:
|
||||
log.info("Stopping the GUI log window ...")
|
||||
self._gui_log_viewer.stop()
|
||||
@@ -1442,6 +1480,10 @@ class SerenaAgent:
|
||||
if self._dashboard_manager:
|
||||
self._dashboard_manager.shutdown()
|
||||
self._dashboard_manager = None
|
||||
if self._active_project is not None:
|
||||
log.info(f"Shutting down active project '{self._active_project.project_name}' ...")
|
||||
self._active_project.shutdown(timeout=timeout)
|
||||
self._active_project = None
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
|
||||
+2
-5
@@ -489,11 +489,8 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
log.info("Starting Serena project server")
|
||||
log.info("Storing logs in %s", log_path)
|
||||
|
||||
server = ProjectServer()
|
||||
run_kwargs: dict[str, Any] = {"host": host}
|
||||
if port is not None:
|
||||
run_kwargs["port"] = port
|
||||
server.run(**run_kwargs)
|
||||
server = ProjectServer(host=host, port=port)
|
||||
server.run()
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
|
||||
@@ -207,7 +207,7 @@ class SerenaDashboardAPI:
|
||||
self._tool_names = tool_names
|
||||
self._agent = agent
|
||||
self._host = host
|
||||
self._app = Flask(__name__)
|
||||
self._app = Flask(self.__class__.__name__)
|
||||
if trusted_hosts:
|
||||
self._app.config["TRUSTED_HOSTS"] = trusted_hosts
|
||||
self._tool_usage_stats = tool_usage_stats
|
||||
@@ -966,6 +966,8 @@ class SerenaDashboardTrayManager:
|
||||
HOST = "127.0.0.1"
|
||||
"""listen address (local only)"""
|
||||
|
||||
TRUSTED_HOSTS = ["localhost", "127.0.0.1"]
|
||||
|
||||
ALIVE_CHECK_INTERVAL_SECONDS = 3
|
||||
"""interval in seconds between alive checks of registered instances"""
|
||||
|
||||
@@ -984,7 +986,8 @@ class SerenaDashboardTrayManager:
|
||||
self._lock = threading.Lock()
|
||||
self._tray_icon: Optional["pystray.Icon"] = None
|
||||
self._alive_check_use_pid = alive_check_use_pid
|
||||
self._app = Flask(__name__)
|
||||
self._app = Flask(self.__class__.__name__)
|
||||
self._app.config["TRUSTED_HOSTS"] = self.TRUSTED_HOSTS
|
||||
self._setup_routes()
|
||||
self._use_pywebview = use_pywebview
|
||||
|
||||
@@ -1152,6 +1155,7 @@ class SerenaDashboardTrayManager:
|
||||
for port in dead_ports:
|
||||
self._instances.pop(port, None)
|
||||
log.info("Removed unreachable instance on port %d", port)
|
||||
self._update_menu()
|
||||
|
||||
# terminate if no instances remain
|
||||
with self._lock:
|
||||
|
||||
@@ -323,6 +323,7 @@ class PreToolUseRemindAboutSymbolicToolsHook(PreToolUseHook):
|
||||
".lua",
|
||||
".m",
|
||||
".matlab",
|
||||
".nf",
|
||||
".php",
|
||||
".proto",
|
||||
".ps1",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import requests as requests_lib
|
||||
@@ -44,19 +45,33 @@ class ProjectServer:
|
||||
|
||||
PORT = SerenaPorts.PROJECT_SERVER_PORT
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, host: str = "127.0.0.1", port: int | None = None) -> None:
|
||||
"""
|
||||
:param host: the host address to listen on.
|
||||
:param port: the port to listen on; if None, use default
|
||||
"""
|
||||
from serena.agent import SerenaAgent
|
||||
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
serena_config.gui_log_window = False
|
||||
serena_config.web_dashboard = False
|
||||
if port is None:
|
||||
port = self.PORT
|
||||
|
||||
serena_config = SerenaConfig.from_config_file().with_headless_mode_overrides()
|
||||
serena_config.language_backend = LanguageBackend.LSP
|
||||
|
||||
self._agent = SerenaAgent(serena_config=serena_config)
|
||||
self._loaded_projects_by_root: dict[str, "Project"] = {}
|
||||
self._project_load_locks_by_root: dict[str, threading.Lock] = {}
|
||||
self._active_project_lock = threading.Lock()
|
||||
self._loaded_projects_lock = threading.Lock()
|
||||
self._port = port
|
||||
self._host = host
|
||||
|
||||
# create the Flask application
|
||||
# create the Flask application, limiting trusted hosts for the case where the server is running on localhost
|
||||
self._app = Flask(__name__)
|
||||
local_hosts = ["localhost", "127.0.0.1"]
|
||||
if self._host in local_hosts:
|
||||
self._app.config["TRUSTED_HOSTS"] = local_hosts
|
||||
|
||||
self._setup_routes()
|
||||
|
||||
def _setup_routes(self) -> None:
|
||||
@@ -78,31 +93,50 @@ class ProjectServer:
|
||||
|
||||
key = str(registered_project.project_root)
|
||||
|
||||
if key in self._loaded_projects_by_root:
|
||||
return self._loaded_projects_by_root[key]
|
||||
# find or publish the per-project load lock while holding the shared dictionaries
|
||||
with self._loaded_projects_lock:
|
||||
project = self._loaded_projects_by_root.get(key)
|
||||
if project is not None:
|
||||
return project
|
||||
project_load_lock = self._project_load_locks_by_root.get(key)
|
||||
if project_load_lock is None:
|
||||
project_load_lock = threading.Lock()
|
||||
self._project_load_locks_by_root[key] = project_load_lock
|
||||
|
||||
with LogTime(f"Loading project '{project_root_or_name}'"):
|
||||
project = registered_project.get_project_instance(serena_config)
|
||||
project.create_language_server_manager()
|
||||
self._loaded_projects_by_root[key] = project
|
||||
return project
|
||||
# initialize only this project; another project's cached lookup or cold load can proceed
|
||||
with project_load_lock:
|
||||
with self._loaded_projects_lock:
|
||||
project = self._loaded_projects_by_root.get(key)
|
||||
if project is not None:
|
||||
return project
|
||||
|
||||
with LogTime(f"Loading project '{project_root_or_name}'"):
|
||||
project = registered_project.get_project_instance(serena_config)
|
||||
project.create_language_server_manager()
|
||||
|
||||
with self._loaded_projects_lock:
|
||||
self._loaded_projects_by_root[key] = project
|
||||
return project
|
||||
|
||||
def _query_project(self, req: QueryProjectRequest) -> str:
|
||||
"""Handle a /query_project request by invoking the agent on the specified project and tool."""
|
||||
"""Handle a /query_project request by invoking the agent on the specified project and tool.
|
||||
|
||||
The active project is process-wide state, whereas ``apply_ex`` runs the tool on the
|
||||
agent's task executor thread. Without the lock, a second request entering
|
||||
``active_project_context`` while the first request's tool is still executing would
|
||||
redirect that tool to the wrong project (and restore the wrong project afterwards).
|
||||
"""
|
||||
project = self._get_project(req.project_name)
|
||||
with self._agent.active_project_context(project):
|
||||
with self._active_project_lock, self._agent.active_project_context(project):
|
||||
tool = self._agent.get_tool_by_name(req.tool_name)
|
||||
if not tool.is_readonly():
|
||||
raise ValueError(f"Tool '{req.tool_name}' is not read-only and cannot be executed via the query_project route")
|
||||
params = json.loads(req.tool_params_json)
|
||||
return tool.apply_ex(**params)
|
||||
|
||||
def run(self, host: str = "127.0.0.1", port: int = PORT) -> int:
|
||||
"""Run the server on the given host and port.
|
||||
|
||||
:param host: the host address to listen on.
|
||||
:param port: the port to listen on.
|
||||
:return: the port number the server is running on.
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Run the server on the given host and port.
|
||||
"""
|
||||
from flask import cli
|
||||
|
||||
@@ -111,8 +145,7 @@ class ProjectServer:
|
||||
# replacement, even one with an identical signature), so the monkeypatch is suppressed here
|
||||
cli.show_server_banner = lambda *args, **kwargs: None # ty: ignore[invalid-assignment]
|
||||
|
||||
self._app.run(host=host, port=port, debug=False, use_reloader=False, threaded=True)
|
||||
return port
|
||||
self._app.run(host=self._host, port=self._port, debug=False, use_reloader=False, threaded=True)
|
||||
|
||||
|
||||
class ProjectServerClient:
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
# 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.
|
||||
# See Serena's documentation for more details on concept of contexts:
|
||||
# https://oraios.github.io/serena/02-usage/050_configuration.html#contexts
|
||||
description: Description of the context (meta-information only)
|
||||
|
||||
# define a prompt which will be included in the system prompt when agents are started in this context.
|
||||
# See: https://oraios.github.io/serena/02-usage/050_configuration.html#prompt-templates
|
||||
prompt: |
|
||||
This prompt will form part of the system prompt/initial instructions for agents started in this context.
|
||||
Add as much content as needed.
|
||||
Leave it undefined if no prompt is needed, i.e.
|
||||
prompt: null
|
||||
|
||||
# tools to be excluded in this context.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
excluded_tools: []
|
||||
|
||||
# several tools are excluded by default and have to be explicitly included by the user
|
||||
# included tools that would otherwise be excluded (particularly optional tools that are disabled by default).
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
included_optional_tools: []
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
fixed_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: {}
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
# See Serena's documentation for more details on concept of modes.
|
||||
# See Serena's documentation for more details on concept of modes:
|
||||
# https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
description: Description of the mode (meta-information only)
|
||||
|
||||
# define a prompt which will be included in the system prompt when this mode is activated
|
||||
# (or will be provided in the project activation message if the mode is activated dynamically).
|
||||
# See: https://oraios.github.io/serena/02-usage/050_configuration.html#prompt-templates
|
||||
prompt: |
|
||||
Provide a prompt that will form part of the instructions sent to the model when this mode is activated.
|
||||
# tools that are to be excluded by this mode
|
||||
This prompt will form part of the system prompt/initial instructions for agents started in this context.
|
||||
Add as much content as needed.
|
||||
Leave it undefined if no prompt is needed, i.e.
|
||||
prompt: null
|
||||
|
||||
# tools to be excluded in this context.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
excluded_tools: []
|
||||
# several tools are excluded by default and have to be explicitly included by the user
|
||||
included_optional_tools: []
|
||||
|
||||
# included tools that would otherwise be excluded (particularly optional tools that are disabled by default).
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
included_optional_tools: []
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
fixed_tools: []
|
||||
@@ -49,10 +49,8 @@ prompts:
|
||||
The context and modes of operation are described below. These determine how to interact with your user
|
||||
and which kinds of interactions are expected of you.
|
||||
|
||||
Context description:
|
||||
{{ context_system_prompt }}
|
||||
|
||||
Modes descriptions:
|
||||
{% for prompt in mode_system_prompts %}
|
||||
{{ prompt }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -4,18 +4,19 @@ project_name: "project_name"
|
||||
# list of language servers to start when using the LSP backend; choose from:
|
||||
# ada al angular ansible bash
|
||||
# bsl clojure cpp cpp_ccls crystal
|
||||
# csharp csharp_omnisharp cue dart elixir
|
||||
# elm erlang fortran fsharp gdscript
|
||||
# go groovy haskell haxe hlsl
|
||||
# html java json julia kotlin
|
||||
# latex lean4 lua luau markdown
|
||||
# matlab msl nix ocaml pascal
|
||||
# perl php php_phpactor php_phpantom powershell
|
||||
# python python_basedpyright python_jedi python_pyrefly python_ty
|
||||
# qml r rego ruby ruby_solargraph
|
||||
# rust scala scss solidity svelte
|
||||
# swift systemverilog terraform toml typescript
|
||||
# typescript_vts vue yaml zig
|
||||
# csharp csharp_omnisharp cue dart deno
|
||||
# elixir elm erlang fortran fsharp
|
||||
# gdscript go groovy haskell haxe
|
||||
# hlsl html java json julia
|
||||
# kotlin latex lean4 lua luau
|
||||
# markdown matlab msl nextflow nix
|
||||
# ocaml pascal perl php php_phpactor
|
||||
# php_phpantom powershell python python_basedpyright python_jedi
|
||||
# python_pyrefly python_ty qml r rego
|
||||
# ruby ruby_solargraph rust scala scss
|
||||
# solidity svelte swift systemverilog terraform
|
||||
# toml typescript typescript_vts vue yaml
|
||||
# zig
|
||||
# (This list may be outdated; generated with scripts/print_language_list.py;
|
||||
# For the current list, see values of the LanguageServerId enum here:
|
||||
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
|
||||
@@ -25,6 +26,7 @@ project_name: "project_name"
|
||||
# - For JavaScript, use typescript
|
||||
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
|
||||
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
|
||||
# - For Deno projects, use deno (serves the same .ts/.js files as typescript; requires the deno CLI on PATH)
|
||||
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
|
||||
# - For Free Pascal/Lazarus, use pascal
|
||||
# Special requirements:
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Provides Deno-specific instantiation of the LanguageServer class, using the
|
||||
language server built into the Deno CLI (``deno lsp``).
|
||||
"""
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DenoLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Deno instantiation of the LanguageServer class, backed by ``deno lsp``.
|
||||
|
||||
Serves TypeScript/JavaScript in Deno projects. Unlike the plain
|
||||
typescript-language-server, ``deno lsp`` understands Deno-specific module
|
||||
resolution (``npm:`` / ``jsr:`` / ``https:`` imports) and the ``Deno.*``
|
||||
global namespace.
|
||||
|
||||
This server overlaps the TypeScript server on file extensions and is therefore
|
||||
marked experimental: it is not auto-detected and must be selected explicitly via
|
||||
``languages: [deno]`` in ``project.yml``.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def supports_implementation_request(cls) -> bool:
|
||||
return True
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
None,
|
||||
"typescript",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
# node_modules appears in Deno projects using npm compatibility; vendor/ holds
|
||||
# vendored remote dependencies. Neither should be indexed as project sources.
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "dist", "build"]
|
||||
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""Return the path to the ``deno`` executable (the language server ships with it)."""
|
||||
deno_path = shutil.which("deno")
|
||||
if deno_path is None:
|
||||
raise FileNotFoundError(
|
||||
"The 'deno' executable was not found on PATH. Install Deno "
|
||||
"(https://docs.deno.com/runtime/getting_started/installation/) — it bundles "
|
||||
"the language server used here — and ensure 'deno' is on your PATH."
|
||||
)
|
||||
return deno_path
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path, "lsp"]
|
||||
|
||||
def _get_language_id_for_file(self, relative_file_path: str) -> str:
|
||||
# deno lsp relies on the correct languageId; .tsx/.jsx in particular must not
|
||||
# be sent as plain "typescript" or symbol ranges get truncated at JSX expressions.
|
||||
if relative_file_path.endswith(".tsx"):
|
||||
return "typescriptreact"
|
||||
if relative_file_path.endswith(".jsx"):
|
||||
return "javascriptreact"
|
||||
if relative_file_path.endswith((".js", ".mjs", ".cjs")):
|
||||
return "javascript"
|
||||
return "typescript"
|
||||
|
||||
def _create_base_initialize_params(self) -> dict:
|
||||
return {
|
||||
"locale": "en",
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {"didSave": True, "dynamicRegistration": True},
|
||||
"definition": {"dynamicRegistration": True},
|
||||
"references": {"dynamicRegistration": True},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": True,
|
||||
"hierarchicalDocumentSymbolSupport": True,
|
||||
"symbolKind": {"valueSet": list(range(1, 27))},
|
||||
},
|
||||
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
|
||||
"completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
|
||||
"rename": {"dynamicRegistration": True, "prepareSupport": True},
|
||||
"publishDiagnostics": {"relatedInformation": True},
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": True,
|
||||
"configuration": True,
|
||||
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||
"symbol": {"dynamicRegistration": True},
|
||||
},
|
||||
},
|
||||
# deno lsp reads its settings from initializationOptions; enabling the server
|
||||
# and the linter mirrors the defaults of the official VS Code Deno extension.
|
||||
"initializationOptions": {
|
||||
"enable": True,
|
||||
"lint": True,
|
||||
"unstable": False,
|
||||
},
|
||||
}
|
||||
|
||||
def _start_server(self) -> None:
|
||||
"""Start the ``deno lsp`` process and drive the LSP initialize handshake."""
|
||||
|
||||
def register_capability_handler(params: dict) -> None:
|
||||
return
|
||||
|
||||
def window_log_message(msg: dict) -> None:
|
||||
log.info(f"LSP: window/logMessage: {msg}")
|
||||
|
||||
def do_nothing(params: dict) -> None:
|
||||
return
|
||||
|
||||
def configuration_handler(params: dict) -> list:
|
||||
# deno lsp requests workspace/configuration during startup; return an empty
|
||||
# settings object per requested item so it proceeds with its defaults.
|
||||
return [{} for _ in params.get("items", [])]
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_request("workspace/configuration", configuration_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
# Deno-specific notifications emitted after config discovery; no action needed.
|
||||
self.server.on_notification("deno/didRefreshDenoConfigurationTree", do_nothing)
|
||||
self.server.on_notification("deno/didChangeDenoConfiguration", do_nothing)
|
||||
|
||||
log.info("Starting deno lsp server process")
|
||||
self.server.start()
|
||||
initialize_params = self._create_initialize_params()
|
||||
|
||||
log.info("Sending initialize request from LSP client to deno lsp and awaiting response")
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
assert "textDocumentSync" in init_response["capabilities"]
|
||||
assert "definitionProvider" in init_response["capabilities"]
|
||||
assert "documentSymbolProvider" in init_response["capabilities"]
|
||||
assert "referencesProvider" in init_response["capabilities"]
|
||||
|
||||
self.server.notify.initialized({})
|
||||
@@ -35,11 +35,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
GRADLE_ALLOWED_HOSTS = ("services.gradle.org", "github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
VSCODE_JAVA_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
INTELLICODE_ALLOWED_HOSTS = (
|
||||
"visualstudioexptteam.gallery.vsassets.io",
|
||||
"marketplace.visualstudio.com",
|
||||
"download.visualstudio.microsoft.com",
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -77,10 +72,9 @@ class RuntimeDependencyPaths:
|
||||
"""
|
||||
Stores the paths to the runtime dependencies of EclipseJDTLS.
|
||||
|
||||
In the default mode (vscode-java VSIX), all paths are populated.
|
||||
In the upstream-jdtls mode (when ``jdtls_path`` and ``lombok_path`` are set),
|
||||
fields that have no upstream equivalent (gradle distribution, IntelliCode bundle)
|
||||
are set to None.
|
||||
In the default mode (vscode-java VSIX), all paths are populated. In the
|
||||
upstream-jdtls mode (when ``jdtls_path`` and ``lombok_path`` are set), the
|
||||
Gradle distribution path is ``None`` because Serena does not download it.
|
||||
"""
|
||||
|
||||
jre_path: str
|
||||
@@ -89,8 +83,6 @@ class RuntimeDependencyPaths:
|
||||
jdtls_readonly_config_path: str
|
||||
lombok_jar_path: str
|
||||
gradle_path: str | None = None
|
||||
intellicode_jar_path: str | None = None
|
||||
intellisense_members_path: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -113,8 +105,8 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
Two installation modes are supported:
|
||||
|
||||
1. **Default vscode-java VSIX mode** (no extra config required) — Serena downloads the platform-specific
|
||||
vscode-java VSIX bundle (~500 MB: JDTLS + bundled JRE 21 + Lombok + IntelliCode), Gradle distribution
|
||||
and IntelliCode VSIX from public hosts. Suitable when public network access is available.
|
||||
vscode-java VSIX bundle (JDTLS + bundled JRE 21 + Lombok) and Gradle distribution from public hosts.
|
||||
Suitable when public network access is available.
|
||||
|
||||
2. **Upstream JDTLS mode** (activated by setting both ``jdtls_path`` and ``lombok_path``) — uses an
|
||||
existing JDTLS installation and the system JDK. Nothing is downloaded. Suitable for restricted-network
|
||||
@@ -143,8 +135,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
gradle_java_home is unset, Gradle import (default: false)
|
||||
- jdtls_xmx: Maximum heap size for the JDTLS server JVM (default: "3G")
|
||||
- jdtls_xms: Initial heap size for the JDTLS server JVM (default: "100m")
|
||||
- intellicode_xmx: Maximum heap size for the IntelliCode embedded JVM (default: "1G")
|
||||
- intellicode_xms: Initial heap size for the IntelliCode embedded JVM (default: "100m")
|
||||
- lombok_show_generated: Show Lombok-generated methods (getX/setX/builder()/...) in document
|
||||
symbols by sending java.symbols.includeGeneratedCode=true to JDTLS (default: true).
|
||||
Set to false for @Data-heavy projects where the extra getters/setters are noise.
|
||||
@@ -155,7 +145,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
Pinned versions: "1.54.0-923" (default) and "1.42.0-561" (legacy / initial). Other versions
|
||||
are not supported in default VSIX mode (the resource paths inside the archive change between
|
||||
releases); use upstream-jdtls mode for arbitrary versions.
|
||||
- intellicode_version: Override the pinned IntelliCode VSIX version downloaded by Serena
|
||||
- runtimes: Additional JRE/JDK entries to register with JDT-LS's ``java.configuration.runtimes``,
|
||||
for projects whose source/target level exceeds the JDK JDT-LS itself runs on (currently
|
||||
JDK 21 in default vscode-java VSIX mode). Each entry is a mapping with:
|
||||
@@ -190,12 +179,9 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
use_system_java_home: true # set to true to use system JAVA_HOME for JDTLS and Gradle fallback
|
||||
jdtls_xmx: "3G" # maximum heap size for the JDTLS server JVM
|
||||
jdtls_xms: "100m" # initial heap size for the JDTLS server JVM
|
||||
intellicode_xmx: "1G" # maximum heap size for the IntelliCode embedded JVM
|
||||
intellicode_xms: "100m" # initial heap size for the IntelliCode embedded JVM
|
||||
lombok_show_generated: true # show Lombok-generated methods in document symbols (default true)
|
||||
gradle_version: "8.14.2"
|
||||
vscode_java_version: "1.54.0-923" # also accepts pinned legacy "1.42.0-561"
|
||||
intellicode_version: "1.2.30"
|
||||
runtimes: # register additional JDKs for projects targeting a newer Java version
|
||||
- name: "JavaSE-25"
|
||||
path: "/home/user/Java/jdk25"
|
||||
@@ -220,7 +206,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
|
||||
self._service_ready_event = threading.Event()
|
||||
self._project_ready_event = threading.Event()
|
||||
self._intellicode_enable_command_available = threading.Event()
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
ls_resources_dir = self.ls_resources_dir(self._solidlsp_settings)
|
||||
@@ -258,7 +243,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
|
||||
# versions used initially (which use default paths without version suffix)
|
||||
INITIAL_VSCODE_JAVA_VERSION = "1.42.0-561"
|
||||
INITIAL_INTELLICODE_VERSION = "1.2.30"
|
||||
INITIAL_VSCODE_JAVA_PATHS = VsixResourcePaths(
|
||||
jre_version="21.0.7",
|
||||
lombok_jar_basename="lombok-1.18.36.jar",
|
||||
@@ -270,7 +254,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
# scripts/update_downloaded_dependency_hashes.py and commit the resulting changes.
|
||||
DEFAULT_GRADLE_VERSION = "8.14.2"
|
||||
DEFAULT_VSCODE_JAVA_VERSION = "1.54.0-923"
|
||||
DEFAULT_INTELLICODE_VERSION = "1.2.30"
|
||||
DEFAULT_VSCODE_JAVA_PATHS = VsixResourcePaths(
|
||||
jre_version="21.0.10",
|
||||
lombok_jar_basename="lombok-1.18.39-4050.jar",
|
||||
@@ -320,19 +303,9 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
)
|
||||
return deps
|
||||
|
||||
@classmethod
|
||||
def _create_dep_intellicode(cls, intellicode_version: str | None = None) -> DownloadedDependency:
|
||||
intellicode_version = intellicode_version or cls.DEFAULT_INTELLICODE_VERSION
|
||||
return DownloadedDependency(
|
||||
url=f"https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/{intellicode_version}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage",
|
||||
archive_type="zip",
|
||||
allowed_hosts=INTELLICODE_ALLOWED_HOSTS,
|
||||
verified=intellicode_version == cls.DEFAULT_INTELLICODE_VERSION,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_dep_hashes(cls) -> None:
|
||||
deps = [cls._create_dep_gradle(), cls._create_dep_intellicode()] + list(cls._create_deps_vscode_java().values())
|
||||
deps = [cls._create_dep_gradle()] + list(cls._create_deps_vscode_java().values())
|
||||
with DownloadedDependencyHashDatabase.get_instance().update_context() as db:
|
||||
for dep in deps:
|
||||
db.update(dep)
|
||||
@@ -351,8 +324,8 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
``brew install jdtls`` or extracted ``jdt-language-server-*.tar.gz``) and the system JDK.
|
||||
Nothing is downloaded by Serena. Suitable for restricted-network/corporate environments.
|
||||
* **Default vscode-java VSIX mode** (activated otherwise): downloads the platform-specific
|
||||
vscode-java VSIX bundle (containing JDTLS, bundled JRE 21, Lombok, IntelliCode), Gradle
|
||||
distribution and IntelliCode VSIX from public hosts. Original behaviour, unchanged.
|
||||
vscode-java VSIX bundle (containing JDTLS, bundled JRE 21 and Lombok) and a Gradle
|
||||
distribution from public hosts.
|
||||
"""
|
||||
jdtls_path = custom_settings.get("jdtls_path")
|
||||
lombok_path = custom_settings.get("lombok_path")
|
||||
@@ -369,16 +342,12 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
platformId = PlatformUtils.get_platform_id()
|
||||
gradle_version = custom_settings.get("gradle_version", cls.DEFAULT_GRADLE_VERSION)
|
||||
vscode_java_version = custom_settings.get("vscode_java_version", cls.DEFAULT_VSCODE_JAVA_VERSION)
|
||||
intellicode_version = custom_settings.get("intellicode_version", cls.DEFAULT_INTELLICODE_VERSION)
|
||||
|
||||
# install-dir name per the version-pinning convention (see module-level block):
|
||||
# INITIAL -> legacy unversioned dir; everything else -> "{name}-{resolved}" subdir
|
||||
vscode_java_dirname = (
|
||||
"vscode-java" if vscode_java_version == cls.INITIAL_VSCODE_JAVA_VERSION else f"vscode-java-{vscode_java_version}"
|
||||
)
|
||||
intellicode_dirname = (
|
||||
"intellicode" if intellicode_version == cls.INITIAL_INTELLICODE_VERSION else f"intellicode-{intellicode_version}"
|
||||
)
|
||||
|
||||
# Resolve internal VSIX paths (JRE / Lombok / launcher filenames). For pinned versions
|
||||
# these are known; for any other user-supplied version we bail out — guessing would
|
||||
@@ -478,26 +447,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
assert os.path.exists(jdtls_launcher_jar_path)
|
||||
assert os.path.exists(jdtls_readonly_config_path)
|
||||
|
||||
intellicode_dep = cls._create_dep_intellicode(intellicode_version)
|
||||
intellicode_directory_path = str(PurePath(ls_resources_dir, intellicode_dirname))
|
||||
os.makedirs(intellicode_directory_path, exist_ok=True)
|
||||
intellicode_jar_path = str(
|
||||
PurePath(intellicode_directory_path, "extension/dist/com.microsoft.jdtls.intellicode.core-0.7.0.jar")
|
||||
)
|
||||
intellisense_members_path = str(PurePath(intellicode_directory_path, "extension/dist/bundledModels/java_intellisense-members"))
|
||||
if not all(
|
||||
[
|
||||
os.path.exists(intellicode_directory_path),
|
||||
os.path.exists(intellicode_jar_path),
|
||||
os.path.exists(intellisense_members_path),
|
||||
]
|
||||
):
|
||||
intellicode_dep.download_to(intellicode_directory_path)
|
||||
|
||||
assert os.path.exists(intellicode_directory_path)
|
||||
assert os.path.exists(intellicode_jar_path)
|
||||
assert os.path.exists(intellisense_members_path)
|
||||
|
||||
return RuntimeDependencyPaths(
|
||||
gradle_path=gradle_path,
|
||||
lombok_jar_path=lombok_jar_path,
|
||||
@@ -505,8 +454,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
jre_home_path=jre_home_path,
|
||||
jdtls_launcher_jar_path=jdtls_launcher_jar_path,
|
||||
jdtls_readonly_config_path=jdtls_readonly_config_path,
|
||||
intellicode_jar_path=intellicode_jar_path,
|
||||
intellisense_members_path=intellisense_members_path,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -520,7 +467,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
:param jdtls_path: absolute path to the JDTLS root (containing ``plugins/`` and ``config_<platform>/``)
|
||||
:param lombok_path: absolute path to the Lombok jar (mandatory; agent is always attached)
|
||||
:param custom_settings: language-server-specific settings from ls_specific_settings.java
|
||||
:return: populated RuntimeDependencyPaths with gradle/intellicode fields set to None
|
||||
:return: populated RuntimeDependencyPaths with ``gradle_path`` set to ``None``
|
||||
"""
|
||||
# validate jdtls_path structure (root + plugins dir)
|
||||
jdtls_root = Path(jdtls_path)
|
||||
@@ -569,8 +516,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
jdtls_readonly_config_path=str(config_dir),
|
||||
lombok_jar_path=lombok_path,
|
||||
gradle_path=None,
|
||||
intellicode_jar_path=None,
|
||||
intellisense_members_path=None,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -1006,10 +951,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
gradle_user_home = None
|
||||
log.info(f"Gradle user home not found at default location ({default_gradle_home}), will use JDTLS defaults")
|
||||
|
||||
# IntelliCode JVM settings (used in vmargs for the embedded JVM)
|
||||
intellicode_xmx = self._custom_settings.get("intellicode_xmx", "1G")
|
||||
intellicode_xms = self._custom_settings.get("intellicode_xms", "100m")
|
||||
|
||||
# Lombok-generated symbols (getX/setX/builder()/equals/hashCode/toString/...): JDTLS filters
|
||||
# these out of documentSymbol results by default. Without them, find_symbol/get_symbols_overview
|
||||
# return only user-written sources, which breaks navigation around @Data/@Builder/@Getter/@Setter
|
||||
@@ -1194,19 +1135,10 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}},
|
||||
},
|
||||
"initializationOptions": {
|
||||
"bundles": ["intellicode-core.jar"],
|
||||
"bundles": [],
|
||||
"settings": {
|
||||
"java": {
|
||||
"home": None,
|
||||
"jdt": {
|
||||
"ls": {
|
||||
"java": {"home": None},
|
||||
"vmargs": f"-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx{intellicode_xmx} -Xms{intellicode_xms} -Xlog:disable",
|
||||
"lombokSupport": {"enabled": True},
|
||||
"protobufSupport": {"enabled": True},
|
||||
"androidSupport": {"enabled": True},
|
||||
}
|
||||
},
|
||||
"errors": {"incompleteClasspath": {"severity": "error"}},
|
||||
"configuration": {
|
||||
"checkProjectSettingsExclusions": False,
|
||||
@@ -1326,14 +1258,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
|
||||
initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri]
|
||||
|
||||
# IntelliCode bundle: only attached in default vscode-java VSIX mode.
|
||||
# In upstream-jdtls mode (jdtls_path set) we don't ship IntelliCode — agentic Serena workflows
|
||||
# don't use completion ranking, so the bundle would be inert dead weight.
|
||||
if self.runtime_dependency_paths.intellicode_jar_path is not None:
|
||||
initialize_params["initializationOptions"]["bundles"] = [self.runtime_dependency_paths.intellicode_jar_path]
|
||||
else:
|
||||
initialize_params["initializationOptions"]["bundles"] = []
|
||||
|
||||
# merge the bundled JRE with any additional runtimes configured via ls_specific_settings.java.runtimes
|
||||
# (e.g. so projects targeting a newer Java version than the bundled JRE resolve their JRE container)...
|
||||
default_runtime = {"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True}
|
||||
@@ -1378,9 +1302,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"*",
|
||||
" ",
|
||||
]
|
||||
if registration["method"] == "workspace/executeCommand":
|
||||
if "java.intellicode.enable" in registration["registerOptions"]["commands"]:
|
||||
self._intellicode_enable_command_available.set()
|
||||
return
|
||||
|
||||
def lang_status_handler(params: dict) -> None:
|
||||
@@ -1424,23 +1345,6 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
|
||||
self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]}) # type: ignore
|
||||
|
||||
# IntelliCode enablement is only relevant in the default vscode-java VSIX mode where the
|
||||
# IntelliCode bundle is shipped. In upstream-jdtls mode it's absent and the
|
||||
# 'java.intellicode.enable' command will never be registered, so we skip the wait/call.
|
||||
if self.runtime_dependency_paths.intellicode_jar_path is not None:
|
||||
self._intellicode_enable_command_available.wait()
|
||||
|
||||
java_intellisense_members_path = self.runtime_dependency_paths.intellisense_members_path
|
||||
assert java_intellisense_members_path is not None
|
||||
assert os.path.exists(java_intellisense_members_path)
|
||||
intellicode_enable_result = self.server.send.execute_command(
|
||||
{
|
||||
"command": "java.intellicode.enable",
|
||||
"arguments": [True, java_intellisense_members_path],
|
||||
}
|
||||
)
|
||||
assert intellicode_enable_result
|
||||
|
||||
if not self._service_ready_event.is_set():
|
||||
log.info("Waiting for service to be ready ...")
|
||||
self._service_ready_event.wait()
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
"""
|
||||
Provides Gleam specific instantiation of the LanguageServer class.
|
||||
|
||||
The Gleam language server is bundled with the Gleam compiler and is started via
|
||||
``gleam lsp``. No separate language-server package is required beyond the Gleam
|
||||
compiler itself (https://gleam.run).
|
||||
"""
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import (
|
||||
LanguageServerDependencyProvider,
|
||||
LanguageServerDependencyProviderSinglePath,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Gleam LSP emits $/progress (begin/end) while resolving/downloading project
|
||||
# dependencies on first start. Once dependencies are resolved, symbol, reference,
|
||||
# hover and diagnostics queries are served synchronously — no further
|
||||
# compile-wait or per-request retry is needed (this was verified against
|
||||
# gleam 1.17 on Windows: cross-file references return immediately once the
|
||||
# dependency-download progress phase ends).
|
||||
_INITIAL_PROGRESS_BEGIN_TIMEOUT = 10 # seconds to wait for the first $/progress begin
|
||||
_INITIAL_PROGRESS_IDLE_TIMEOUT = 180 # seconds to wait for all progress phases to end
|
||||
|
||||
|
||||
class GleamLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Gleam specific instantiation of the LanguageServer class.
|
||||
|
||||
Uses the language server bundled with the Gleam compiler (``gleam lsp``).
|
||||
Requires the ``gleam`` binary to be installed and available on PATH; see
|
||||
https://gleam.run/getting-started/installing/ for installation instructions.
|
||||
|
||||
The Gleam compiler is a self-contained Rust binary — neither Erlang/OTP nor
|
||||
a separate LSP package is required for symbol, reference, hover, definition,
|
||||
or diagnostics queries. (Full project compilation to BEAM bytecode does
|
||||
require Erlang, but the LSP's analysis path does not.)
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
super().__init__(config, repository_root_path, None, "gleam", solidlsp_settings)
|
||||
self._progress_lock = threading.Lock()
|
||||
self._active_progress_tokens: set[str | int] = set()
|
||||
self._first_progress_seen = threading.Event()
|
||||
self._idle = threading.Event()
|
||||
self._idle.set() # optimistic: assume ready until a $/progress begin arrives
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""
|
||||
Discover the Gleam compiler executable on PATH.
|
||||
|
||||
:return: path to the ``gleam`` binary
|
||||
:raises FileNotFoundError: if ``gleam`` is neither on PATH nor provided via ``ls_path``
|
||||
"""
|
||||
gleam_binary = shutil.which("gleam")
|
||||
if gleam_binary is None:
|
||||
raise FileNotFoundError(
|
||||
"Gleam is not installed or not in PATH.\n"
|
||||
"Please install the Gleam compiler from https://gleam.run/getting-started/installing/\n"
|
||||
"and ensure the 'gleam' binary is available on your PATH.\n"
|
||||
"The Gleam language server is bundled with the compiler and is started via `gleam lsp`."
|
||||
)
|
||||
return gleam_binary
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
# `gleam lsp` runs the language server over stdio.
|
||||
return [core_path, "lsp"]
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
# Gleam writes compiled output and downloaded dependencies under build/.
|
||||
return super().is_ignored_dirname(dirname) or dirname == "build"
|
||||
|
||||
@override
|
||||
def _supports_pull_diagnostics(self) -> bool:
|
||||
# `gleam lsp` does not implement `textDocument/diagnostic` (LSP 3.17 pull diagnostics) and
|
||||
# advertises no `diagnosticProvider` capability. Sending a pull request would hang (the server
|
||||
# neither responds nor errors), so opt out and rely on `textDocument/publishDiagnostics` (push),
|
||||
# which the base class stores via `_observe_server_notification`.
|
||||
return False
|
||||
|
||||
def _create_base_initialize_params(self) -> dict:
|
||||
"""
|
||||
Return the language-specific initialize params for the Gleam language server.
|
||||
|
||||
``processId``, ``rootPath``, ``rootUri`` and ``workspaceFolders`` are populated by the
|
||||
default ``InitializeParamsBuilder`` and must not be set here.
|
||||
"""
|
||||
return {
|
||||
"locale": "en",
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {"didSave": True, "dynamicRegistration": True},
|
||||
"definition": {"dynamicRegistration": True, "linkSupport": True},
|
||||
"references": {"dynamicRegistration": True},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": True,
|
||||
"hierarchicalDocumentSymbolSupport": True,
|
||||
"symbolKind": {"valueSet": list(range(1, 27))},
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": True,
|
||||
"completionItem": {
|
||||
"snippetSupport": True,
|
||||
"documentationFormat": ["markdown", "plaintext"],
|
||||
},
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": True,
|
||||
"contentFormat": ["markdown", "plaintext"],
|
||||
},
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": True,
|
||||
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||
"configuration": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def _download_dependencies(self) -> None:
|
||||
"""Run ``gleam deps download`` so the stdlib is available before the LSP starts.
|
||||
|
||||
Gleam LSP also fetches missing dependencies on start and reports progress via
|
||||
``$/progress``; pre-fetching keeps the readiness wait short and avoids first-request
|
||||
races on slow CI runners.
|
||||
"""
|
||||
gleam_path = shutil.which("gleam")
|
||||
if gleam_path is None:
|
||||
return # _get_or_install_core_dependency will raise a clear error when the server starts
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[gleam_path, "deps", "download"],
|
||||
cwd=self.repository_root_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
log.warning("gleam deps download failed (exit %s): %s", result.returncode, result.stderr[:200])
|
||||
else:
|
||||
log.info("gleam deps download completed")
|
||||
except Exception as e:
|
||||
log.warning("Failed to run gleam deps download: %s", e)
|
||||
|
||||
def _start_server(self) -> None:
|
||||
"""Start the Gleam language server process."""
|
||||
|
||||
def register_capability_handler(_params: dict) -> None:
|
||||
return
|
||||
|
||||
def window_log_message(msg: dict) -> None:
|
||||
log.info("LSP: window/logMessage: %s", msg)
|
||||
|
||||
def on_progress(params: dict) -> None:
|
||||
token = params.get("token")
|
||||
value = params.get("value", {})
|
||||
if not isinstance(value, dict) or token is None:
|
||||
return
|
||||
kind = value.get("kind")
|
||||
with self._progress_lock:
|
||||
if kind == "begin":
|
||||
self._active_progress_tokens.add(token)
|
||||
self._first_progress_seen.set()
|
||||
self._idle.clear()
|
||||
log.info(
|
||||
"Gleam LSP: progress begin (token=%s), active=%d",
|
||||
token,
|
||||
len(self._active_progress_tokens),
|
||||
)
|
||||
elif kind == "end":
|
||||
self._active_progress_tokens.discard(token)
|
||||
if not self._active_progress_tokens:
|
||||
self._idle.set()
|
||||
log.info("Gleam LSP: all progress phases finished")
|
||||
|
||||
def do_nothing(_params: dict) -> None:
|
||||
return
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_notification("$/progress", on_progress)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
|
||||
# Pre-fetch dependencies so the LSP's own dependency-download phase is short.
|
||||
self._download_dependencies()
|
||||
|
||||
log.info("Starting Gleam language server (gleam lsp) process")
|
||||
self.server.start()
|
||||
initialize_params = self._create_initialize_params()
|
||||
|
||||
log.info("Sending initialize request to Gleam LSP and awaiting response")
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
capabilities = init_response.get("capabilities", {}) if isinstance(init_response, dict) else {}
|
||||
log.info("Gleam LSP capabilities: %s", list(capabilities.keys()))
|
||||
|
||||
self.server.notify.initialized({})
|
||||
|
||||
# Wait for Gleam's initial dependency-download progress to finish.
|
||||
# Gleam emits $/progress begin/end for dependency resolution on first start; once all
|
||||
# phases end, queries are served synchronously. If no progress is reported within the
|
||||
# short begin-timeout, dependencies were already resolved and the server is ready.
|
||||
if not self._first_progress_seen.wait(timeout=_INITIAL_PROGRESS_BEGIN_TIMEOUT):
|
||||
log.info(
|
||||
"Gleam LSP: no $/progress within %ss, assuming ready (dependencies already resolved)",
|
||||
_INITIAL_PROGRESS_BEGIN_TIMEOUT,
|
||||
)
|
||||
elif not self._idle.wait(timeout=_INITIAL_PROGRESS_IDLE_TIMEOUT):
|
||||
log.warning(
|
||||
"Gleam LSP: timed out waiting for initial progress after %ss, proceeding anyway",
|
||||
_INITIAL_PROGRESS_IDLE_TIMEOUT,
|
||||
)
|
||||
|
||||
log.info("Gleam language server ready")
|
||||
@@ -5,8 +5,8 @@ You can configure the following options in ls_specific_settings (in serena_confi
|
||||
|
||||
ls_specific_settings:
|
||||
kotlin:
|
||||
ls_path: '/path/to/kotlin-lsp.sh' # Custom path to Kotlin Language Server executable
|
||||
kotlin_lsp_version: '261.13587.0' # Kotlin Language Server version (default: current bundled version)
|
||||
ls_path: '/path/to/bin/intellij-server' # Custom path to Kotlin Language Server executable
|
||||
kotlin_lsp_version: '262.9593.0' # Kotlin Language Server version (default: current bundled version)
|
||||
jvm_options: '-Xmx2G' # JVM options for Kotlin Language Server (default: -Xmx2G)
|
||||
|
||||
Example configuration for large projects:
|
||||
@@ -21,16 +21,18 @@ import os
|
||||
import pathlib
|
||||
import stat
|
||||
import threading
|
||||
from dataclasses import dataclass
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.dependency_provider import DownloadedDependency, DownloadedDependencyHashDatabase
|
||||
from solidlsp.ls import (
|
||||
LanguageServerDependencyProvider,
|
||||
LanguageServerDependencyProviderSinglePath,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -44,34 +46,21 @@ KOTLIN_LSP_ALLOWED_HOSTS = ("download-cdn.jetbrains.com",)
|
||||
# Version pinning convention (see eclipse_jdtls.py for the full spec):
|
||||
# INITIAL_* — frozen forever; legacy unversioned install dir is reserved for it.
|
||||
# DEFAULT_* — bumped on upgrades; goes into a versioned subdir.
|
||||
# NOTE: After changing either pinned version, run scripts/update_downloaded_dependency_hashes.py.
|
||||
INITIAL_KOTLIN_LSP_VERSION = "261.13587.0"
|
||||
INITIAL_KOTLIN_LSP_SHA256_BY_SUFFIX = {
|
||||
"win-x64": "2806c2bd4810bd8e7ccc27d8c0ca4a5232a1c4f26ea1f4ba40e578b60860ccad",
|
||||
"linux-x64": "dc0ed2e70cb0d61fdabb26aefce8299b7a75c0dcfffb9413715e92caec6e83ec",
|
||||
"linux-aarch64": "d1dceb000fe06c5e2c30b95e7f4ab01d05101bd03ed448167feeb544a9f1d651",
|
||||
"mac-x64": "a3972f27229eba2c226060e54baea1c958c82c326dfc971bf53f72a74d0564a3",
|
||||
"mac-aarch64": "d4ea28b22b29cf906fe16d23698a8468f11646a6a66dcb15584f306aaefbee6c",
|
||||
}
|
||||
DEFAULT_KOTLIN_LSP_VERSION = "261.13587.0"
|
||||
DEFAULT_KOTLIN_LSP_SHA256_BY_SUFFIX = {
|
||||
"win-x64": "2806c2bd4810bd8e7ccc27d8c0ca4a5232a1c4f26ea1f4ba40e578b60860ccad",
|
||||
"linux-x64": "dc0ed2e70cb0d61fdabb26aefce8299b7a75c0dcfffb9413715e92caec6e83ec",
|
||||
"linux-aarch64": "d1dceb000fe06c5e2c30b95e7f4ab01d05101bd03ed448167feeb544a9f1d651",
|
||||
"mac-x64": "a3972f27229eba2c226060e54baea1c958c82c326dfc971bf53f72a74d0564a3",
|
||||
"mac-aarch64": "d4ea28b22b29cf906fe16d23698a8468f11646a6a66dcb15584f306aaefbee6c",
|
||||
}
|
||||
DEFAULT_KOTLIN_LSP_VERSION = "262.9593.0"
|
||||
|
||||
# Versions before this one use kotlin-lsp-{version}-{platform}.zip and a kotlin-lsp script.
|
||||
# Starting with 262.4739.0, JetBrains publishes kotlin-server archives with platform-specific
|
||||
# formats, a bundled JBR, and bin/intellij-server as the launcher.
|
||||
KOTLIN_SERVER_PACKAGING_MIN_VERSION = (262, 4739, 0)
|
||||
|
||||
def _kotlin_lsp_sha(version: str, kotlin_suffix: str) -> str | None:
|
||||
if version == INITIAL_KOTLIN_LSP_VERSION:
|
||||
return INITIAL_KOTLIN_LSP_SHA256_BY_SUFFIX.get(kotlin_suffix)
|
||||
if version == DEFAULT_KOTLIN_LSP_VERSION:
|
||||
return DEFAULT_KOTLIN_LSP_SHA256_BY_SUFFIX.get(kotlin_suffix)
|
||||
return None
|
||||
# The first modern archives remained under the legacy /kotlin-lsp CDN path.
|
||||
# Starting with 262.8190.0, JetBrains moved them to /language-server/kotlin-server.
|
||||
KOTLIN_SERVER_CDN_PATH_MIN_VERSION = (262, 8190, 0)
|
||||
|
||||
|
||||
# Platform-specific Kotlin LSP download suffixes
|
||||
PLATFORM_KOTLIN_SUFFIX = {
|
||||
# Platform-specific suffixes used by legacy Kotlin LSP ZIP archives.
|
||||
LEGACY_PLATFORM_KOTLIN_SUFFIX = {
|
||||
"win-x64": "win-x64",
|
||||
"linux-x64": "linux-x64",
|
||||
"linux-arm64": "linux-aarch64",
|
||||
@@ -79,6 +68,22 @@ PLATFORM_KOTLIN_SUFFIX = {
|
||||
"osx-arm64": "mac-aarch64",
|
||||
}
|
||||
|
||||
# Modern archive filename suffix, FileUtils archive type, and launcher path within the archive.
|
||||
KOTLIN_SERVER_ARTIFACT_BY_PLATFORM: dict[str, tuple[str, FileUtils.ArchiveType, tuple[str, ...]]] = {
|
||||
"win-x64": (".win.zip", "zip", ("bin", "intellij-server.exe")),
|
||||
"win-arm64": ("-aarch64.win.zip", "zip", ("bin", "intellij-server.exe")),
|
||||
"linux-x64": (".tar.gz", "gztar", ("kotlin-server-{version}", "bin", "intellij-server")),
|
||||
"linux-arm64": ("-aarch64.tar.gz", "gztar", ("kotlin-server-{version}", "bin", "intellij-server")),
|
||||
"osx-x64": (".sit", "zip", ("kotlin-server-{version}", "bin", "intellij-server")),
|
||||
"osx-arm64": ("-aarch64.sit", "zip", ("kotlin-server-{version}", "bin", "intellij-server")),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class KotlinLSPArtifact:
|
||||
dependency: DownloadedDependency
|
||||
launcher_parts: tuple[str, ...]
|
||||
|
||||
|
||||
class KotlinLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
@@ -102,15 +107,78 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
# set again once all progress tokens have ended.
|
||||
self._indexing_complete = threading.Event()
|
||||
self._indexing_complete.set()
|
||||
self._intellij_server_ready = threading.Event()
|
||||
self._active_progress_tokens: set[str] = set()
|
||||
self._progress_lock = threading.Lock()
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir, str(self.cache_dir))
|
||||
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
def __init__(self, custom_settings: SolidLSPSettings.CustomLSSettings, ls_resources_dir: str):
|
||||
def __init__(self, custom_settings: SolidLSPSettings.CustomLSSettings, ls_resources_dir: str, project_cache_dir: str):
|
||||
super().__init__(custom_settings, ls_resources_dir)
|
||||
self._project_cache_dir = project_cache_dir
|
||||
|
||||
@classmethod
|
||||
def _create_artifact(cls, version: str, platform_id: PlatformId) -> KotlinLSPArtifact:
|
||||
"""Build download and launcher metadata for one Kotlin LSP release.
|
||||
|
||||
JetBrains has three publishing layouts: legacy ZIPs before 262.4739.0,
|
||||
modern platform archives under ``/kotlin-lsp`` through 262.7569.0,
|
||||
and modern archives under ``/language-server/kotlin-server`` from
|
||||
262.8190.0 onward. Serena verifies the frozen initial and current default
|
||||
releases; arbitrary user-selected versions are unverified by design.
|
||||
"""
|
||||
try:
|
||||
version_parts = tuple(int(part) for part in version.split("."))
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Kotlin LSP version must contain only dot-separated integers: {version!r}") from exc
|
||||
|
||||
verified = version in {INITIAL_KOTLIN_LSP_VERSION, DEFAULT_KOTLIN_LSP_VERSION}
|
||||
if version_parts >= KOTLIN_SERVER_PACKAGING_MIN_VERSION:
|
||||
artifact_config = KOTLIN_SERVER_ARTIFACT_BY_PLATFORM.get(platform_id.value)
|
||||
if artifact_config is None:
|
||||
raise ValueError(f"Unsupported platform for Kotlin LSP {version}: {platform_id.value}")
|
||||
|
||||
asset_suffix, archive_type, launcher_parts = artifact_config
|
||||
asset_name = f"kotlin-server-{version}{asset_suffix}"
|
||||
cdn_path = "language-server/kotlin-server" if version_parts >= KOTLIN_SERVER_CDN_PATH_MIN_VERSION else "kotlin-lsp"
|
||||
return KotlinLSPArtifact(
|
||||
dependency=DownloadedDependency(
|
||||
url=f"https://download-cdn.jetbrains.com/{cdn_path}/{version}/{asset_name}",
|
||||
archive_type=archive_type,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
verified=verified,
|
||||
),
|
||||
launcher_parts=tuple(part.format(version=version) for part in launcher_parts),
|
||||
)
|
||||
|
||||
kotlin_suffix = LEGACY_PLATFORM_KOTLIN_SUFFIX.get(platform_id.value)
|
||||
if kotlin_suffix is None:
|
||||
raise ValueError(f"Unsupported platform for Kotlin LSP {version}: {platform_id.value}")
|
||||
|
||||
return KotlinLSPArtifact(
|
||||
dependency=DownloadedDependency(
|
||||
url=f"https://download-cdn.jetbrains.com/kotlin-lsp/{version}/kotlin-lsp-{version}-{kotlin_suffix}.zip",
|
||||
archive_type="zip",
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
verified=verified,
|
||||
),
|
||||
launcher_parts=("kotlin-lsp.cmd",) if platform_id.is_windows() else ("kotlin-lsp.sh",),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_dep_hashes(cls) -> None:
|
||||
pinned_artifacts = [
|
||||
*(cls._create_artifact(INITIAL_KOTLIN_LSP_VERSION, PlatformId(platform)) for platform in LEGACY_PLATFORM_KOTLIN_SUFFIX),
|
||||
*(
|
||||
cls._create_artifact(DEFAULT_KOTLIN_LSP_VERSION, PlatformId(platform))
|
||||
for platform in KOTLIN_SERVER_ARTIFACT_BY_PLATFORM
|
||||
),
|
||||
]
|
||||
with DownloadedDependencyHashDatabase.get_instance().update_context() as database:
|
||||
for artifact in pinned_artifacts:
|
||||
database.update(artifact.dependency)
|
||||
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""
|
||||
@@ -118,16 +186,9 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
# Verify platform support
|
||||
assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), (
|
||||
"Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment"
|
||||
)
|
||||
|
||||
kotlin_suffix = PLATFORM_KOTLIN_SUFFIX.get(platform_id.value)
|
||||
assert kotlin_suffix, f"Unsupported platform for Kotlin LSP: {platform_id.value}"
|
||||
|
||||
# Setup paths for dependencies; legacy unversioned dir reserved for INITIAL only
|
||||
kotlin_lsp_version = self._custom_settings.get("kotlin_lsp_version", DEFAULT_KOTLIN_LSP_VERSION)
|
||||
artifact = self._create_artifact(kotlin_lsp_version, platform_id)
|
||||
ls_dirname = (
|
||||
"kotlin_language_server"
|
||||
if kotlin_lsp_version == INITIAL_KOTLIN_LSP_VERSION
|
||||
@@ -137,22 +198,13 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
os.makedirs(static_dir, exist_ok=True)
|
||||
|
||||
# Setup Kotlin Language Server
|
||||
kotlin_script_name = "kotlin-lsp.cmd" if platform_id.value.startswith("win-") else "kotlin-lsp.sh"
|
||||
kotlin_script = os.path.join(static_dir, kotlin_script_name)
|
||||
kotlin_script = os.path.join(static_dir, *artifact.launcher_parts)
|
||||
|
||||
if not os.path.exists(kotlin_script):
|
||||
kotlin_url = f"https://download-cdn.jetbrains.com/kotlin-lsp/{kotlin_lsp_version}/kotlin-lsp-{kotlin_lsp_version}-{kotlin_suffix}.zip"
|
||||
expected_sha256 = _kotlin_lsp_sha(kotlin_lsp_version, kotlin_suffix)
|
||||
log.info("Downloading Kotlin Language Server...")
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
kotlin_url,
|
||||
static_dir,
|
||||
"zip",
|
||||
expected_sha256=expected_sha256,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
)
|
||||
artifact.dependency.download_to(static_dir)
|
||||
|
||||
if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"):
|
||||
if os.path.exists(kotlin_script) and not platform_id.is_windows():
|
||||
os.chmod(
|
||||
kotlin_script,
|
||||
stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH,
|
||||
@@ -165,10 +217,18 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
return kotlin_script
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path, "--stdio"]
|
||||
command = [core_path, "--stdio"]
|
||||
# A custom ls_path is independent of Serena's managed version. Select
|
||||
# arguments from the actual launcher so existing kotlin-lsp.sh/.cmd
|
||||
# configurations do not inherit IntelliJ-server-only options.
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
intellij_launcher_name = "intellij-server.exe" if platform_id.is_windows() else "intellij-server"
|
||||
if os.path.basename(core_path).lower() == intellij_launcher_name:
|
||||
command.extend(["--system-path", os.path.join(self._project_cache_dir, "kotlin-lsp-system")])
|
||||
return command
|
||||
|
||||
def create_launch_command_env(self) -> dict[str, str]:
|
||||
"""Provides JAVA_HOME and JVM options for the Kotlin Language Server process."""
|
||||
"""Provides JVM options for the Kotlin Language Server process."""
|
||||
env: dict[str, str] = {}
|
||||
|
||||
# Get JVM options from settings or use default
|
||||
@@ -427,6 +487,7 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Starts the Kotlin Language Server
|
||||
"""
|
||||
self._intellij_server_ready.clear()
|
||||
|
||||
def execute_client_command_handler(params: dict) -> list:
|
||||
return []
|
||||
@@ -437,6 +498,11 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
def window_log_message(msg: dict) -> None:
|
||||
log.info(f"LSP: window/logMessage: {msg}")
|
||||
|
||||
def intellij_server_ready(_params: dict | None) -> None:
|
||||
"""Mark the modern IntelliJ-based server ready after its startup/import phase."""
|
||||
log.info("Kotlin IntelliJ language server reported that it is ready")
|
||||
self._intellij_server_ready.set()
|
||||
|
||||
def work_done_progress_create(params: dict) -> dict:
|
||||
"""Handle window/workDoneProgress/create: the server is about to report async progress.
|
||||
Clear the indexing-complete event so _start_server waits until all tokens finish.
|
||||
@@ -487,9 +553,12 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
self.server.on_request("workspace/codeLens/refresh", do_nothing)
|
||||
self.server.on_notification("language/status", do_nothing)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_notification("window/showMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_request("workspace/diagnostic/refresh", do_nothing)
|
||||
self.server.on_request("window/workDoneProgress/create", work_done_progress_create)
|
||||
self.server.on_notification("$/progress", progress_handler)
|
||||
self.server.on_notification("intellij/ready-for-test", intellij_server_ready)
|
||||
self.server.on_notification("$/logTrace", do_nothing)
|
||||
self.server.on_notification("$/cancelRequest", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
@@ -503,6 +572,8 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
capabilities = init_response["capabilities"]
|
||||
server_info = init_response.get("serverInfo", {})
|
||||
wait_for_intellij_ready = isinstance(server_info, dict) and server_info.get("name") == "IntelliJ Language Server by JetBrains"
|
||||
assert "textDocumentSync" in capabilities, "Server must support textDocumentSync"
|
||||
assert "hoverProvider" in capabilities, "Server must support hover"
|
||||
assert "completionProvider" in capabilities, "Server must support code completion"
|
||||
@@ -515,14 +586,17 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
|
||||
self.server.notify.initialized({})
|
||||
|
||||
# Wait for any async indexing to complete.
|
||||
# Wait for workspace import and async indexing to complete.
|
||||
# - Older KLS (0.253.x): indexing is synchronous inside `initialize`, no $/progress is sent,
|
||||
# _indexing_complete stays SET -> wait() returns immediately.
|
||||
# - Newer KLS (261+): server sends window/workDoneProgress/create after initialized,
|
||||
# which clears the event; wait() blocks until all progress tokens end.
|
||||
# - IntelliJ-based KLS (262.4739+): server sends intellij/ready-for-test after
|
||||
# its workspace import and indexing phase completes.
|
||||
_INDEXING_TIMEOUT = 120.0
|
||||
log.info("Waiting for Kotlin LSP indexing to complete (if async)...")
|
||||
if self._indexing_complete.wait(timeout=_INDEXING_TIMEOUT):
|
||||
ready_event = self._intellij_server_ready if wait_for_intellij_ready else self._indexing_complete
|
||||
if ready_event.wait(timeout=_INDEXING_TIMEOUT):
|
||||
log.info("Kotlin LSP ready")
|
||||
else:
|
||||
log.warning("Kotlin LSP did not signal indexing completion within %.0fs; proceeding anyway", _INDEXING_TIMEOUT)
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
"""
|
||||
Provides Nextflow specific instantiation of the LanguageServer class, using the official
|
||||
Nextflow language server (https://github.com/nextflow-io/language-server), which is
|
||||
distributed as a fat JAR and requires a Java runtime.
|
||||
|
||||
You can configure the following options in ls_specific_settings (in serena_config.yml):
|
||||
|
||||
ls_specific_settings:
|
||||
nextflow:
|
||||
ls_path: '/path/to/language-server-all.jar' # use an existing JAR instead of downloading one
|
||||
nextflow_ls_version: '26.04.3' # JAR version to download (default: bundled version)
|
||||
java_home: '/path/to/jdk' # JDK to run the JAR with (default: JAVA_HOME or PATH)
|
||||
jvm_options: ['-Xmx2G'] # JVM options for the language server process
|
||||
exclude_patterns: ['work', '.nextflow'] # workspace paths the language server shall ignore
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
from collections.abc import Hashable
|
||||
from pathlib import Path
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.dependency_provider import DownloadedDependency, DownloadedDependencyHashDatabase
|
||||
from solidlsp.ls import (
|
||||
LanguageServerDependencyProvider,
|
||||
LanguageServerDependencyProviderSinglePath,
|
||||
RawDocumentSymbol,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.lsp_protocol_handler import lsp_types
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
NEXTFLOW_LS_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
|
||||
# NOTE: after bumping this, re-run scripts/update_downloaded_dependency_hashes.py and commit the
|
||||
# updated src/solidlsp/resources/downloaded_dependency_hashes.json.
|
||||
DEFAULT_NEXTFLOW_LS_VERSION = "26.04.3"
|
||||
|
||||
NEXTFLOW_LS_JAR_NAME = "language-server-all.jar"
|
||||
|
||||
#: the Nextflow language server is built for Java 17+
|
||||
MIN_JDK_VERSION = 17
|
||||
|
||||
#: paths that hold Nextflow's own outputs rather than pipeline sources; scanning them is pure overhead
|
||||
DEFAULT_EXCLUDE_PATTERNS = ["work", ".nextflow"]
|
||||
|
||||
#: the server scans the workspace only when a configuration change asks it to, and it compares the new
|
||||
#: configuration against its own defaults (see LanguageServerConfiguration.defaults() upstream, where the
|
||||
#: error reporting mode is "warnings"), so the settings we push must differ in at least one of the keys
|
||||
#: that trigger a scan
|
||||
_SCAN_TRIGGERING_ERROR_REPORTING_MODE = "errors"
|
||||
|
||||
#: upper bound for the initial workspace scan, which compiles every .nf file in the project
|
||||
_WORKSPACE_SCAN_TIMEOUT = 180.0
|
||||
|
||||
#: declaration keywords the server prepends to symbol names (see ScriptSymbolProvider.getSymbolName upstream)
|
||||
_SYMBOL_NAME_PREFIXES = frozenset(("process", "workflow", "function", "record", "enum"))
|
||||
|
||||
|
||||
class NextflowLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Nextflow specific instantiation of the LanguageServer class.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
"""
|
||||
Creates a Nextflow Language Server instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
None,
|
||||
"nextflow",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
# The workspace scan is asynchronous and reported via $/progress under the "initialize" token;
|
||||
# requests answered before it ends see an empty AST cache.
|
||||
self._scan_complete = threading.Event()
|
||||
self._active_progress_tokens: set[str] = set()
|
||||
self._progress_lock = threading.Lock()
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
@classmethod
|
||||
def _create_dep_language_server(cls, version: str | None = None) -> DownloadedDependency:
|
||||
version = version or DEFAULT_NEXTFLOW_LS_VERSION
|
||||
return DownloadedDependency(
|
||||
url=f"https://github.com/nextflow-io/language-server/releases/download/v{version}/{NEXTFLOW_LS_JAR_NAME}",
|
||||
allowed_hosts=NEXTFLOW_LS_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_dep_hashes(cls) -> None:
|
||||
deps = [cls._create_dep_language_server()]
|
||||
with DownloadedDependencyHashDatabase.get_instance().update_context() as db:
|
||||
for dep in deps:
|
||||
db.update(dep)
|
||||
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
version = self._custom_settings.get("nextflow_ls_version", DEFAULT_NEXTFLOW_LS_VERSION)
|
||||
install_dir = os.path.join(self._ls_resources_dir, f"nextflow_language_server-{version}")
|
||||
jar_path = os.path.join(install_dir, NEXTFLOW_LS_JAR_NAME)
|
||||
|
||||
if not os.path.exists(jar_path):
|
||||
os.makedirs(install_dir, exist_ok=True)
|
||||
log.info("Downloading Nextflow Language Server %s ...", version)
|
||||
self._create_dep_language_server(version).download_to(jar_path)
|
||||
|
||||
if not os.path.exists(jar_path):
|
||||
raise FileNotFoundError(f"Nextflow Language Server JAR not found at {jar_path}")
|
||||
return jar_path
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
java_exe = self._resolve_java()
|
||||
jvm_options = self._custom_settings.get("jvm_options", [])
|
||||
if not isinstance(jvm_options, list):
|
||||
log.warning("The 'jvm_options' setting should be a list of strings. Ignoring the provided value: %s", jvm_options)
|
||||
jvm_options = []
|
||||
return [java_exe, *jvm_options, "-jar", core_path]
|
||||
|
||||
def _resolve_java(self) -> str:
|
||||
"""
|
||||
Locates a ``java`` executable (``java_home`` setting -> ``JAVA_HOME`` -> PATH) and verifies
|
||||
that it is new enough to run the language server.
|
||||
|
||||
:return: the path to the java executable
|
||||
"""
|
||||
java_exe_name = "java.exe" if os.name == "nt" else "java"
|
||||
|
||||
java_exe: str | None = None
|
||||
if explicit_home := self._custom_settings.get("java_home"):
|
||||
candidate = str(Path(explicit_home) / "bin" / java_exe_name)
|
||||
if not os.path.exists(candidate):
|
||||
raise SolidLSPException(
|
||||
f"java_home='{explicit_home}' is invalid: '{candidate}' does not exist. "
|
||||
f"Set ls_specific_settings.nextflow.java_home to a JDK home that contains bin/{java_exe_name}."
|
||||
)
|
||||
java_exe = candidate
|
||||
elif env_home := os.environ.get("JAVA_HOME"):
|
||||
candidate = str(Path(env_home) / "bin" / java_exe_name)
|
||||
if os.path.exists(candidate):
|
||||
java_exe = candidate
|
||||
else:
|
||||
log.warning("JAVA_HOME='%s' invalid (no '%s'), falling back to PATH.", env_home, candidate)
|
||||
if java_exe is None:
|
||||
java_exe = shutil.which("java")
|
||||
if java_exe is None:
|
||||
raise SolidLSPException(
|
||||
"Could not locate a Java installation for the Nextflow language server. "
|
||||
"Set ls_specific_settings.nextflow.java_home, set the JAVA_HOME environment variable, "
|
||||
f"or ensure 'java' is on PATH. Required: JDK {MIN_JDK_VERSION}+."
|
||||
)
|
||||
|
||||
# reuse JDTLS's JVM interrogation, which reports the real java.home even for stub launchers
|
||||
from solidlsp.language_servers.eclipse_jdtls import EclipseJDTLS
|
||||
|
||||
java_home, major_version = EclipseJDTLS.DependencyProvider._inspect_java(java_exe)
|
||||
if major_version < MIN_JDK_VERSION:
|
||||
raise SolidLSPException(
|
||||
f"The Nextflow language server requires JDK {MIN_JDK_VERSION}+ but '{java_exe}' is JDK {major_version} "
|
||||
f"(java.home={java_home}). Install a newer JDK and update ls_specific_settings.nextflow.java_home "
|
||||
"or JAVA_HOME."
|
||||
)
|
||||
log.info("Using JDK %d at %s for the Nextflow language server", major_version, java_exe)
|
||||
return java_exe
|
||||
|
||||
def _create_base_initialize_params(self) -> dict:
|
||||
"""
|
||||
Returns the initialize params for the Nextflow Language Server.
|
||||
"""
|
||||
return {
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {"dynamicRegistration": True, "didSave": True},
|
||||
"completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": False}},
|
||||
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
|
||||
"definition": {"dynamicRegistration": True, "linkSupport": True},
|
||||
"references": {"dynamicRegistration": True},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": True,
|
||||
"symbolKind": {"valueSet": list(range(1, 27))},
|
||||
"hierarchicalDocumentSymbolSupport": True,
|
||||
},
|
||||
"documentLink": {"dynamicRegistration": True},
|
||||
"callHierarchy": {"dynamicRegistration": True},
|
||||
"rename": {"dynamicRegistration": True, "prepareSupport": True},
|
||||
"publishDiagnostics": {"relatedInformation": True},
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": True,
|
||||
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||
"symbol": {"dynamicRegistration": True, "symbolKind": {"valueSet": list(range(1, 27))}},
|
||||
},
|
||||
"window": {"workDoneProgress": True},
|
||||
},
|
||||
}
|
||||
|
||||
def _create_configuration_settings(self) -> dict:
|
||||
"""
|
||||
Returns the settings pushed via workspace/didChangeConfiguration, which is what makes the server
|
||||
scan the workspace (see ``_start_server``).
|
||||
"""
|
||||
exclude_patterns = self._custom_settings.get("exclude_patterns", DEFAULT_EXCLUDE_PATTERNS)
|
||||
if not isinstance(exclude_patterns, list):
|
||||
log.warning("The 'exclude_patterns' setting should be a list of strings. Ignoring the provided value: %s", exclude_patterns)
|
||||
exclude_patterns = DEFAULT_EXCLUDE_PATTERNS
|
||||
return {
|
||||
"nextflow": {
|
||||
"debug": False,
|
||||
# deliberately not the server's default of "warnings": the value must differ from the
|
||||
# defaults for the configuration change to trigger the workspace scan
|
||||
"errorReportingMode": _SCAN_TRIGGERING_ERROR_REPORTING_MODE,
|
||||
"files": {"exclude": exclude_patterns},
|
||||
}
|
||||
}
|
||||
|
||||
def _start_server(self) -> None:
|
||||
"""
|
||||
Starts the Nextflow Language Server.
|
||||
"""
|
||||
|
||||
def do_nothing(params: dict) -> None:
|
||||
return
|
||||
|
||||
def window_log_message(msg: dict) -> None:
|
||||
log.info(f"LSP: window/logMessage: {msg}")
|
||||
|
||||
def work_done_progress_create(params: dict) -> dict:
|
||||
token = str(params.get("token", ""))
|
||||
with self._progress_lock:
|
||||
self._active_progress_tokens.add(token)
|
||||
self._scan_complete.clear()
|
||||
return {}
|
||||
|
||||
def progress_handler(params: dict) -> None:
|
||||
token = str(params.get("token", ""))
|
||||
value = params.get("value", {})
|
||||
kind = value.get("kind")
|
||||
if kind == "begin":
|
||||
with self._progress_lock:
|
||||
self._active_progress_tokens.add(token)
|
||||
self._scan_complete.clear()
|
||||
log.info("Nextflow LS progress [%s]: started - %s", token, value.get("title", ""))
|
||||
elif kind == "report":
|
||||
log.debug("Nextflow LS progress [%s]: %s", token, value.get("message", ""))
|
||||
elif kind == "end":
|
||||
with self._progress_lock:
|
||||
self._active_progress_tokens.discard(token)
|
||||
if not self._active_progress_tokens:
|
||||
self._scan_complete.set()
|
||||
log.info("Nextflow LS progress [%s]: ended", token)
|
||||
|
||||
self.server.on_request("client/registerCapability", do_nothing)
|
||||
self.server.on_request("window/workDoneProgress/create", work_done_progress_create)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_notification("$/progress", progress_handler)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
|
||||
log.info("Starting Nextflow server process")
|
||||
self.server.start()
|
||||
|
||||
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
|
||||
init_response = self.server.send.initialize(self._create_initialize_params())
|
||||
|
||||
capabilities = init_response["capabilities"]
|
||||
assert "textDocumentSync" in capabilities, "Server must support textDocumentSync"
|
||||
assert "hoverProvider" in capabilities, "Server must support hover"
|
||||
assert "completionProvider" in capabilities, "Server must support code completion"
|
||||
assert "definitionProvider" in capabilities, "Server must support go to definition"
|
||||
assert "referencesProvider" in capabilities, "Server must support find references"
|
||||
assert "documentSymbolProvider" in capabilities, "Server must support document symbols"
|
||||
assert "workspaceSymbolProvider" in capabilities, "Server must support workspace symbols"
|
||||
|
||||
self.server.notify.initialized({})
|
||||
|
||||
# The server does not index anything on `initialized`; it (re-)scans the workspace only when a
|
||||
# configuration change arrives whose error reporting mode, exclude patterns or plugin registry
|
||||
# URL differ from what it currently holds. Without this notification, every symbol request
|
||||
# answers with an empty result.
|
||||
self._scan_complete.clear()
|
||||
self.server.notify.workspace_did_change_configuration({"settings": self._create_configuration_settings()})
|
||||
|
||||
log.info("Waiting for the Nextflow workspace scan to complete...")
|
||||
if self._scan_complete.wait(timeout=_WORKSPACE_SCAN_TIMEOUT):
|
||||
log.info("Nextflow LS ready")
|
||||
else:
|
||||
log.warning("Nextflow LS did not signal scan completion within %.0fs; proceeding anyway", _WORKSPACE_SCAN_TIMEOUT)
|
||||
|
||||
@override
|
||||
def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None:
|
||||
"""
|
||||
Flushes the server's pending recompile before asking for references.
|
||||
|
||||
Every ``didOpen``/``didChange`` schedules a debounced (1s) update of the file's AST, and
|
||||
``LanguageService.references`` is the one request that does not await it (``documentSymbol``,
|
||||
``codeLens``, ``documentLink`` and ``semanticTokensFull`` all do). A references request issued
|
||||
within the debounce window therefore races the recompile of the very file it asks about and can
|
||||
come back empty. Sending a ``documentSymbol`` request for the same file first is a cheap way to
|
||||
block until the update has been applied.
|
||||
"""
|
||||
self.server.send.document_symbol({"textDocument": {"uri": self._resolve_file_uri(relative_file_path)}})
|
||||
return super()._send_references_request(relative_file_path, line, column)
|
||||
|
||||
@override
|
||||
def _get_wait_time_for_cross_file_referencing(self) -> float:
|
||||
"""Small safety buffer; the actual synchronisation happens in _send_references_request."""
|
||||
return 1.0
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
"""
|
||||
Strips the declaration keyword the language server prefixes to every symbol name
|
||||
("process GREET", "workflow SAY_HELLO", "function foo"), so that symbols can be addressed
|
||||
by the name they are written with in the source. The implicit entry workflow keeps its
|
||||
"<entry>" placeholder name, which is what the server calls it.
|
||||
"""
|
||||
name = symbol["name"]
|
||||
prefix, sep, rest = name.partition(" ")
|
||||
if sep and prefix in _SYMBOL_NAME_PREFIXES:
|
||||
return rest
|
||||
return name
|
||||
@@ -10,6 +10,7 @@ from enum import Enum
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.initialize_params import DefaultInitializeParamsBuilder, InitializeParamsBuilder
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_utils import PlatformUtils
|
||||
@@ -28,6 +29,56 @@ DEFAULT_METALS_VERSION = "1.6.4"
|
||||
DEFAULT_CLIENT_NAME = "Serena"
|
||||
DEFAULT_ON_STALE_LOCK = "auto-clean"
|
||||
DEFAULT_LOG_MULTI_INSTANCE_NOTICE = True
|
||||
DEFAULT_AUTO_IMPORT_BUILD = True
|
||||
DEFAULT_PROJECT_ROOT_SCAN_DEPTH = 3
|
||||
|
||||
# The `window/showMessageRequest` actions Serena answers affirmatively: the ones standing between
|
||||
# an un-imported workspace and a build server. Everything else is dismissed, since a prompt we do
|
||||
# not recognise is one whose consequences we cannot judge — `Messages.OldBloopVersionRunning`
|
||||
# offers to kill a process, `Messages.NewScalaProject` to open a window. "Don't show again" is
|
||||
# never chosen: Metals persists that dismissal in the project's own state.
|
||||
# (scala/meta/internal/metals/Messages.scala, at `ImportBuild`, `ImportBuildChanges`,
|
||||
# `GenerateBspAndConnect`.)
|
||||
#
|
||||
# Not answered, deliberately: `Messages.ChooseBuildTool` ("Multiple build definitions found. Which
|
||||
# would you like to use?"), whose actions are the build tools' own executable names. It precedes
|
||||
# the import prompt in a workspace holding more than one kind of build, so such a workspace is
|
||||
# still not imported — but choosing a build tool for the user is a guess of a different order, and
|
||||
# Metals offers no way to say "whichever you would have picked".
|
||||
BUILD_IMPORT_PROMPT_ACTIONS = ("Import build", "Import changes", "Connect")
|
||||
|
||||
# Files whose presence marks a directory as the root of a build Metals can import, following the
|
||||
# per-build-tool probes in Metals' `BuildTools` (scala/meta/internal/builds/BuildTools.scala) and
|
||||
# `BazelBuildTool.workspaceSupportsBsp`. Deliberately partial: the probes that need to read a file's
|
||||
# contents (scala-cli's BSP scope) are left out, since missing a build root only returns us to the
|
||||
# previous behaviour, whereas a false positive would hide the real builds beneath it.
|
||||
BUILD_ROOT_MARKER_FILES = (
|
||||
"MODULE.bazel",
|
||||
"WORKSPACE",
|
||||
"build.gradle",
|
||||
"build.gradle.kts",
|
||||
"build.mill",
|
||||
"build.mill.scala",
|
||||
"build.mill.yaml",
|
||||
"build.sbt",
|
||||
"build.sc",
|
||||
"deder.pkl",
|
||||
"mill",
|
||||
"mill.bat",
|
||||
"pom.xml",
|
||||
"project.scala",
|
||||
"settings.gradle",
|
||||
"settings.gradle.kts",
|
||||
)
|
||||
|
||||
# Directories which, when they hold a JSON file, mark an already-configured Metals project
|
||||
# (`BuildTools.hasJsonFile`). An empty one is a leftover, not a build.
|
||||
BUILD_ROOT_MARKER_JSON_DIRS = (".bloop", ".bsp")
|
||||
|
||||
# Directories not worth descending into when scanning for build roots: build output, dependencies,
|
||||
# and the directories belonging to a build we would have recognised at their parent. They are still
|
||||
# probed themselves — only the descent below them is skipped.
|
||||
BUILD_ROOT_SCAN_SKIP_DIRS = frozenset({"node_modules", "out", "project", "src", "target", "venv"})
|
||||
|
||||
|
||||
class StaleLockMode(Enum):
|
||||
@@ -43,6 +94,150 @@ class StaleLockMode(Enum):
|
||||
"""Raise an error and refuse to start."""
|
||||
|
||||
|
||||
def choose_show_message_request_action(params: dict, auto_import_build: bool = DEFAULT_AUTO_IMPORT_BUILD) -> dict | None:
|
||||
"""
|
||||
Choose Serena's answer to a `window/showMessageRequest`, which Metals uses to ask whether to
|
||||
import the build.
|
||||
|
||||
:param params: the request's `ShowMessageRequestParams`
|
||||
:param auto_import_build: whether to answer the build-import prompts affirmatively
|
||||
:return: the action to select, or None to select none — which is what the LSP spec provides
|
||||
for, and what leaving the request unanswered fails to say
|
||||
"""
|
||||
message = params.get("message", "")
|
||||
actions = params.get("actions") or []
|
||||
if auto_import_build:
|
||||
for action in actions:
|
||||
if isinstance(action, dict) and action.get("title") in BUILD_IMPORT_PROMPT_ACTIONS:
|
||||
log.info(f"Metals asked: {message!r}; answering {action['title']!r}")
|
||||
return action
|
||||
offered = [action.get("title") for action in actions if isinstance(action, dict)]
|
||||
log.info(f"Metals asked: {message!r}; dismissing (offered: {offered})")
|
||||
return None
|
||||
|
||||
|
||||
def _contains_json_file(path: str) -> bool:
|
||||
try:
|
||||
return any(entry.name.endswith(".json") for entry in os.scandir(path))
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _is_build_root(path: str) -> bool:
|
||||
"""
|
||||
Whether `path` is the root of a build that Metals can import.
|
||||
"""
|
||||
if any(os.path.isfile(os.path.join(path, name)) for name in BUILD_ROOT_MARKER_FILES):
|
||||
return True
|
||||
if any(_contains_json_file(os.path.join(path, name)) for name in BUILD_ROOT_MARKER_JSON_DIRS):
|
||||
return True
|
||||
# sbt allows the build to be defined entirely under project/, with no build.sbt
|
||||
build_properties = os.path.join(path, "project", "build.properties")
|
||||
if os.path.isfile(build_properties):
|
||||
try:
|
||||
with open(build_properties, encoding="utf-8", errors="replace") as f:
|
||||
return any(line.lstrip().startswith("sbt.version") for line in f)
|
||||
except OSError:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def find_build_roots(repository_root_path: str, max_depth: int = DEFAULT_PROJECT_ROOT_SCAN_DEPTH) -> list[str]:
|
||||
"""
|
||||
Find the roots of the builds contained in the given repository.
|
||||
|
||||
Metals serves one build per workspace folder, so a repository holding several builds
|
||||
(or a single build below its root) must name those directories rather than the repository root.
|
||||
|
||||
:param repository_root_path: the repository root
|
||||
:param max_depth: how many directory levels below the repository root to search;
|
||||
the search does not descend into a directory that is itself a build root
|
||||
:return: the absolute paths of the build roots found, or `[repository_root_path]` if there are none
|
||||
(which leaves Metals' own behaviour unchanged)
|
||||
"""
|
||||
if _is_build_root(repository_root_path):
|
||||
return [repository_root_path]
|
||||
|
||||
roots: list[str] = []
|
||||
visited: set[str] = set()
|
||||
|
||||
def scan(directory: str, depth: int) -> None:
|
||||
# symlinks are followed, as Metals' own search does, so guard against cycles
|
||||
real_path = os.path.realpath(directory)
|
||||
if depth > max_depth or real_path in visited:
|
||||
return
|
||||
visited.add(real_path)
|
||||
try:
|
||||
entries = sorted(os.scandir(directory), key=lambda e: e.name)
|
||||
except OSError:
|
||||
return
|
||||
for entry in entries:
|
||||
if entry.name.startswith(".") or not entry.is_dir():
|
||||
continue
|
||||
if _is_build_root(entry.path):
|
||||
roots.append(entry.path)
|
||||
elif entry.name not in BUILD_ROOT_SCAN_SKIP_DIRS:
|
||||
scan(entry.path, depth + 1)
|
||||
|
||||
scan(repository_root_path, 1)
|
||||
return roots or [repository_root_path]
|
||||
|
||||
|
||||
class ScalaInitializeParamsBuilder(DefaultInitializeParamsBuilder):
|
||||
"""
|
||||
Sends the repository's build roots as the workspace folders, so that Metals creates one
|
||||
service per build (see `MetalsLanguageServer.initialize`), in place of `ls_workspace_folders`,
|
||||
which is about what SolidLSP indexes and is shared across a project's language servers.
|
||||
|
||||
`ls_additional_workspace_folders` is still honoured: those folders can lie outside the
|
||||
repository and so could never be detected, which is the whole point of the setting.
|
||||
"""
|
||||
|
||||
def __init__(self, ls: SolidLanguageServer, build_roots: list[str]):
|
||||
super().__init__(ls, set_workspace_folders=False)
|
||||
self._build_roots = build_roots
|
||||
|
||||
@override
|
||||
def _apply_updates(self) -> None:
|
||||
super()._apply_updates()
|
||||
folders = list(self._build_roots)
|
||||
for path in self._ls.config.get_absolute_additional_workspace_folders(self._ls.repository_root_path):
|
||||
if path not in folders:
|
||||
folders.append(path)
|
||||
log.info("Workspace folders sent to Metals: %s", folders)
|
||||
self._set("workspaceFolders", [self._create_workspace_folder_entry(path) for path in folders])
|
||||
|
||||
|
||||
def _parse_project_roots(value: object) -> list[str] | None:
|
||||
"""
|
||||
Validate the `project_roots` setting, returning None (i.e. detect them) if it is unusable.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str) or not isinstance(value, list) or not all(isinstance(item, str) for item in value):
|
||||
log.warning(f"Invalid project_roots value {value!r}, expected a list of paths; detecting the build roots instead")
|
||||
return None
|
||||
roots: list[str] = [item for item in value if isinstance(item, str)]
|
||||
if not roots:
|
||||
log.warning("Empty project_roots; detecting the build roots instead")
|
||||
return None
|
||||
return roots
|
||||
|
||||
|
||||
def _parse_project_root_scan_depth(value: object) -> int:
|
||||
"""
|
||||
Validate the `project_root_scan_depth` setting, falling back to the default if it is unusable.
|
||||
"""
|
||||
if value is None:
|
||||
return DEFAULT_PROJECT_ROOT_SCAN_DEPTH
|
||||
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
|
||||
log.warning(
|
||||
f"Invalid project_root_scan_depth value {value!r}, expected a positive integer; using {DEFAULT_PROJECT_ROOT_SCAN_DEPTH}"
|
||||
)
|
||||
return DEFAULT_PROJECT_ROOT_SCAN_DEPTH
|
||||
return value
|
||||
|
||||
|
||||
def _get_scala_settings(solidlsp_settings: SolidLSPSettings) -> dict[str, object]:
|
||||
"""
|
||||
Extract Scala-specific settings with defaults applied.
|
||||
@@ -52,6 +247,9 @@ def _get_scala_settings(solidlsp_settings: SolidLSPSettings) -> dict[str, object
|
||||
- client_name: str
|
||||
- on_stale_lock: StaleLockMode
|
||||
- log_multi_instance_notice: bool
|
||||
- auto_import_build: bool
|
||||
- project_roots: list[str] | None
|
||||
- project_root_scan_depth: int
|
||||
"""
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
@@ -60,6 +258,9 @@ def _get_scala_settings(solidlsp_settings: SolidLSPSettings) -> dict[str, object
|
||||
"client_name": DEFAULT_CLIENT_NAME,
|
||||
"on_stale_lock": StaleLockMode.AUTO_CLEAN,
|
||||
"log_multi_instance_notice": DEFAULT_LOG_MULTI_INSTANCE_NOTICE,
|
||||
"auto_import_build": DEFAULT_AUTO_IMPORT_BUILD,
|
||||
"project_roots": None,
|
||||
"project_root_scan_depth": DEFAULT_PROJECT_ROOT_SCAN_DEPTH,
|
||||
}
|
||||
|
||||
if not solidlsp_settings.ls_specific_settings:
|
||||
@@ -80,6 +281,9 @@ def _get_scala_settings(solidlsp_settings: SolidLSPSettings) -> dict[str, object
|
||||
"client_name": scala_settings.get("client_name", DEFAULT_CLIENT_NAME),
|
||||
"on_stale_lock": on_stale_lock,
|
||||
"log_multi_instance_notice": scala_settings.get("log_multi_instance_notice", DEFAULT_LOG_MULTI_INSTANCE_NOTICE),
|
||||
"auto_import_build": scala_settings.get("auto_import_build", DEFAULT_AUTO_IMPORT_BUILD),
|
||||
"project_roots": _parse_project_roots(scala_settings.get("project_roots")),
|
||||
"project_root_scan_depth": _parse_project_root_scan_depth(scala_settings.get("project_root_scan_depth")),
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +304,24 @@ class ScalaLanguageServer(SolidLanguageServer):
|
||||
metals_version: '1.6.4'
|
||||
# Client identifier sent to Metals (default: DEFAULT_CLIENT_NAME)
|
||||
client_name: 'Serena'
|
||||
# Answer Metals' build-import prompts affirmatively, which lets it run the project's
|
||||
# build tool (e.g. sbt bloopInstall). Set false to leave the build un-imported.
|
||||
auto_import_build: true
|
||||
# Build roots to serve, relative to the repository root; when unset, they are
|
||||
# auto-detected (see find_build_roots)
|
||||
project_roots: ['backend', 'tooling/plugin']
|
||||
# How many levels below the repository root auto-detection searches
|
||||
project_root_scan_depth: 3
|
||||
|
||||
Build import:
|
||||
Metals asks, via `window/showMessageRequest`, whether to import a workspace it has not
|
||||
seen before; until that is answered it has no build server and so no build target, and
|
||||
every cross-file query is served by the fallback presentation compiler.
|
||||
|
||||
Monorepo support:
|
||||
Metals serves one build per workspace folder, so the build roots — not the repository
|
||||
root — are what it must be given. They are detected automatically; `project_roots`
|
||||
overrides the detection where it guesses wrong.
|
||||
|
||||
Multi-instance support:
|
||||
Metals uses H2 AUTO_SERVER mode (enabled by default) to support multiple
|
||||
@@ -113,8 +335,14 @@ class ScalaLanguageServer(SolidLanguageServer):
|
||||
Creates a ScalaLanguageServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
self._build_roots = self._resolve_build_roots(repository_root_path, solidlsp_settings)
|
||||
log.info(f"Metals will be given these build roots as workspace folders: {self._build_roots}")
|
||||
|
||||
# Check for stale locks before setting up dependencies (fail-fast)
|
||||
self._check_metals_db_status(repository_root_path, solidlsp_settings)
|
||||
for build_root in self._build_roots:
|
||||
self._check_metals_db_status(build_root, solidlsp_settings)
|
||||
|
||||
self._auto_import_build: bool = _get_scala_settings(solidlsp_settings)["auto_import_build"] # type: ignore[assignment]
|
||||
|
||||
scala_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
|
||||
super().__init__(
|
||||
@@ -125,9 +353,37 @@ class ScalaLanguageServer(SolidLanguageServer):
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
def _check_metals_db_status(self, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
|
||||
@staticmethod
|
||||
def _resolve_build_roots(repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> list[str]:
|
||||
"""
|
||||
Check the Metals H2 database status and handle stale locks.
|
||||
Determine the build roots to serve, from the `project_roots` setting if given and by
|
||||
detection otherwise.
|
||||
"""
|
||||
settings = _get_scala_settings(solidlsp_settings)
|
||||
configured_roots: list[str] | None = settings["project_roots"] # type: ignore[assignment]
|
||||
if configured_roots is None:
|
||||
scan_depth: int = settings["project_root_scan_depth"] # type: ignore[assignment]
|
||||
return find_build_roots(repository_root_path, scan_depth)
|
||||
|
||||
roots = []
|
||||
for root in configured_roots:
|
||||
abs_root = os.path.abspath(os.path.join(repository_root_path, root))
|
||||
if os.path.isdir(abs_root):
|
||||
roots.append(abs_root)
|
||||
else:
|
||||
log.warning(f"Configured Scala project root does not exist, skipping: {abs_root}")
|
||||
if not roots:
|
||||
log.error("No configured Scala project root exists; detecting the build roots instead")
|
||||
return find_build_roots(repository_root_path, settings["project_root_scan_depth"]) # type: ignore[arg-type]
|
||||
return roots
|
||||
|
||||
@override
|
||||
def _create_initialize_params_builder(self) -> InitializeParamsBuilder:
|
||||
return ScalaInitializeParamsBuilder(self, self._build_roots)
|
||||
|
||||
def _check_metals_db_status(self, build_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
|
||||
"""
|
||||
Check the Metals H2 database status of one build root and handle stale locks.
|
||||
|
||||
This method is called before setting up runtime dependencies to fail-fast
|
||||
if there's a stale lock that the user has configured to fail on.
|
||||
@@ -141,7 +397,7 @@ class ScalaLanguageServer(SolidLanguageServer):
|
||||
cleanup_stale_lock,
|
||||
)
|
||||
|
||||
project_path = Path(repository_root_path)
|
||||
project_path = Path(build_root_path)
|
||||
status, lock_info = check_metals_db_status(project_path)
|
||||
|
||||
# Get settings using the shared helper function
|
||||
@@ -300,10 +556,15 @@ class ScalaLanguageServer(SolidLanguageServer):
|
||||
}
|
||||
return initialize_params
|
||||
|
||||
def _answer_show_message_request(self, params: dict) -> dict | None:
|
||||
return choose_show_message_request_action(params, auto_import_build=self._auto_import_build)
|
||||
|
||||
def _start_server(self) -> None:
|
||||
"""
|
||||
Starts the Scala Language Server
|
||||
"""
|
||||
self.server.on_request("window/showMessageRequest", self._answer_show_message_request)
|
||||
|
||||
log.info("Starting Scala server process")
|
||||
self.server.start()
|
||||
|
||||
|
||||
@@ -185,6 +185,17 @@ class LanguageServerId(str, Enum):
|
||||
Supports .qml files. Requires Qt 6 installation providing qmlls on PATH.
|
||||
See https://doc.qt.io/qt-6/qtqml-tool-qmlls.html
|
||||
"""
|
||||
GLEAM = "gleam"
|
||||
"""Gleam language server bundled with the Gleam compiler (`gleam lsp`).
|
||||
Supports .gleam files. Requires the `gleam` binary on PATH.
|
||||
See https://gleam.run/getting-started/installing/ for installation.
|
||||
"""
|
||||
NEXTFLOW = "nextflow"
|
||||
"""Nextflow language server (https://github.com/nextflow-io/language-server), the one that backs the
|
||||
official VS Code extension. Supports .nf scripts (Nextflow .config files are parsed by the server, but
|
||||
it reports no symbols for them, so they are not treated as source files here).
|
||||
Automatically downloads the language server JAR; requires Java 17+ (JAVA_HOME or 'java' on PATH).
|
||||
"""
|
||||
# Experimental or deprecated Language Servers
|
||||
TYPESCRIPT_VTS = "typescript_vts"
|
||||
"""Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
|
||||
@@ -286,6 +297,14 @@ class LanguageServerId(str, Enum):
|
||||
project.yml — Angular LS supersedes both for Angular projects.
|
||||
Must be explicitly specified in project.yml.
|
||||
"""
|
||||
DENO = "deno"
|
||||
"""Deno's built-in language server (``deno lsp``) for Deno TypeScript/JavaScript projects.
|
||||
Understands Deno module resolution (``npm:`` / ``jsr:`` / ``https:`` imports) and the
|
||||
``Deno.*`` globals, which the plain typescript-language-server does not. Overlaps the
|
||||
TypeScript server on file extensions (.ts/.tsx/.js/.jsx/.mts/.cts/.mjs/.cjs), so it is
|
||||
experimental and must be explicitly specified via ``languages: [deno]`` in project.yml;
|
||||
do not also enable typescript for the same files. Requires the ``deno`` CLI on PATH.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def iter_all(cls, include_experimental: bool = True, include_non_programming_languages: bool = True) -> Iterable[Self]:
|
||||
@@ -321,6 +340,7 @@ class LanguageServerId(str, Enum):
|
||||
self.HTML,
|
||||
self.SCSS,
|
||||
self.ANGULAR,
|
||||
self.DENO,
|
||||
}
|
||||
|
||||
def is_programming_language(self) -> bool:
|
||||
@@ -572,6 +592,12 @@ class LanguageServerId(str, Enum):
|
||||
return FilenameMatcher(".gd", ".gdscript")
|
||||
case self.QML:
|
||||
return FilenameMatcher(".qml")
|
||||
case self.GLEAM:
|
||||
return FilenameMatcher(".gleam")
|
||||
case self.NEXTFLOW:
|
||||
# only scripts: the language server does have a service for .config files, but it provides
|
||||
# no symbols for them, so treating them as source files would only pollute the symbol index
|
||||
return FilenameMatcher(".nf")
|
||||
case self.HTML:
|
||||
return FilenameMatcher(".html", ".htm")
|
||||
case self.SCSS:
|
||||
@@ -588,6 +614,14 @@ class LanguageServerId(str, Enum):
|
||||
for postfix in ["x", ""]:
|
||||
path_patterns.append(f".{prefix}ts{postfix}")
|
||||
return FilenameMatcher(*path_patterns)
|
||||
case self.DENO:
|
||||
# Deno serves the same TS/JS family as the TypeScript server.
|
||||
path_patterns = []
|
||||
for prefix in ["c", "m", ""]:
|
||||
for postfix in ["x", ""]:
|
||||
for base_pattern in ["ts", "js"]:
|
||||
path_patterns.append(f".{prefix}{base_pattern}{postfix}")
|
||||
return FilenameMatcher(*path_patterns)
|
||||
case _:
|
||||
raise ValueError(f"Unhandled language: {self}")
|
||||
|
||||
@@ -859,6 +893,14 @@ class LanguageServerId(str, Enum):
|
||||
from solidlsp.language_servers.qml_language_server import QmlLanguageServer
|
||||
|
||||
return QmlLanguageServer
|
||||
case self.GLEAM:
|
||||
from solidlsp.language_servers.gleam_language_server import GleamLanguageServer
|
||||
|
||||
return GleamLanguageServer
|
||||
case self.NEXTFLOW:
|
||||
from solidlsp.language_servers.nextflow_language_server import NextflowLanguageServer
|
||||
|
||||
return NextflowLanguageServer
|
||||
case self.HTML:
|
||||
from solidlsp.language_servers.vscode_html_language_server import VsCodeHtmlLanguageServer
|
||||
|
||||
@@ -871,6 +913,10 @@ class LanguageServerId(str, Enum):
|
||||
from solidlsp.language_servers.angular_language_server import AngularLanguageServer
|
||||
|
||||
return AngularLanguageServer
|
||||
case self.DENO:
|
||||
from solidlsp.language_servers.deno_language_server import DenoLanguageServer
|
||||
|
||||
return DenoLanguageServer
|
||||
case _:
|
||||
raise ValueError(f"Unhandled language: {self}")
|
||||
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
{
|
||||
"https://services.gradle.org/distributions/gradle-8.14.2-bin.zip": "7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999",
|
||||
"https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/1.2.30/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage": "7f61a7f96d101cdf230f96821be3fddd8f890ebfefb3695d18beee43004ae251",
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-darwin-arm64-1.54.0-923.vsix": "c54c45cb0d2579d8e0a4ddeb24d4a9dd0b460d07d9366adea2b38a1da22a463c",
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-darwin-x64-1.54.0-923.vsix": "dfc98abc4e54165a78372e280242a039671729b1b03420608df3b10c6b629fb6",
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-linux-arm64-1.54.0-923.vsix": "e2bb22c427d90da8dbb1afff72ff1e2dce38d50b76deb02d7bc313a330a1330c",
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-linux-x64-1.54.0-923.vsix": "9d4b15da54e25a0192f9bac073f086c015397d3676623b68dbf83a5dbaf5132b",
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-win32-x64-1.54.0-923.vsix": "66f3914987edeccfee8a2558470e0fde4f8c4154232ff4baa5d73373ebc819d4"
|
||||
}
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-win32-x64-1.54.0-923.vsix": "66f3914987edeccfee8a2558470e0fde4f8c4154232ff4baa5d73373ebc819d4",
|
||||
"https://github.com/nextflow-io/language-server/releases/download/v26.04.3/language-server-all.jar": "20cfa34f6e202d6b8babd8d786202ce00e0d39b70ccec3290e2ab3fbd02bc016",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-win-x64.zip": "2806c2bd4810bd8e7ccc27d8c0ca4a5232a1c4f26ea1f4ba40e578b60860ccad",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-linux-x64.zip": "dc0ed2e70cb0d61fdabb26aefce8299b7a75c0dcfffb9413715e92caec6e83ec",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-linux-aarch64.zip": "d1dceb000fe06c5e2c30b95e7f4ab01d05101bd03ed448167feeb544a9f1d651",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-mac-x64.zip": "a3972f27229eba2c226060e54baea1c958c82c326dfc971bf53f72a74d0564a3",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-mac-aarch64.zip": "d4ea28b22b29cf906fe16d23698a8468f11646a6a66dcb15584f306aaefbee6c",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0.win.zip": "f2daaa476f26d99301b406f76de6d87c437d04dc72f06845154619d8f991c51f",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0-aarch64.win.zip": "73a552a6a420158622e5ad8d96b53da8aa8ced3f88a24fded01575927a2fd8e7",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0.tar.gz": "2d99d8e198fbe4aa8f4481e37799724ce94803b4ea12a60b416040e3fcd7cc5e",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0-aarch64.tar.gz": "2317831c6e5607d05b7ebc1da655330125ce0e3d66fbf24517dfce442debc14e",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0.sit": "17369fda97c85418ac24ab38a9df56b21522a3468dfe193832fe455c13920745",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0-aarch64.sit": "6ba6021a706b21e64cef33f7e2b79f187c0910320722bb2d3ed05ad1115ec43f"
|
||||
}
|
||||
@@ -286,6 +286,7 @@ _LANGUAGE_PYTEST_MARKERS: dict[LanguageServerId, list[MarkDecorator | Mark]] = {
|
||||
LanguageServerId.CPP_CCLS: [pytest.mark.cpp],
|
||||
LanguageServerId.CUE: [pytest.mark.cue],
|
||||
LanguageServerId.CSHARP: [pytest.mark.csharp],
|
||||
LanguageServerId.DENO: [pytest.mark.deno],
|
||||
LanguageServerId.FSHARP: [pytest.mark.fsharp],
|
||||
LanguageServerId.GO: [pytest.mark.go],
|
||||
LanguageServerId.HAXE: [pytest.mark.haxe],
|
||||
@@ -436,6 +437,11 @@ def _determine_disabled_language_servers() -> list[LanguageServerId]:
|
||||
# catching a CI setup regression. On Windows/macOS CI (never installed) and off-CI without the binary it skips.
|
||||
if (_sh.which("qmlls6") is None and _sh.which("qmlls") is None) and not (is_ci and is_linux):
|
||||
result.append(LanguageServerId.QML)
|
||||
# gleam is installed (see pytest.yml) on the Ubuntu other-langs CI batch. Same rationale as
|
||||
# qmlls: a missing binary on Linux CI is NOT skipped (fails loudly on a CI setup regression);
|
||||
# Windows/macOS CI and off-CI without the binary skip.
|
||||
if _sh.which("gleam") is None and not (is_ci and is_linux):
|
||||
result.append(LanguageServerId.GLEAM)
|
||||
|
||||
# === 3. Disabled wherever the precondition is missing (including on CI) ===
|
||||
# 3a. Platform precondition: these language servers have no native Windows support.
|
||||
@@ -477,6 +483,8 @@ def _determine_disabled_language_servers() -> list[LanguageServerId]:
|
||||
result.append(LanguageServerId.OCAML)
|
||||
if not _is_perl_language_server_available(): # perl ships with the OS; the LS module is the real signal
|
||||
result.append(LanguageServerId.PERL)
|
||||
if _sh.which("deno") is None: # deno bundles the language server (`deno lsp`); skip where the CLI is absent
|
||||
result.append(LanguageServerId.DENO)
|
||||
|
||||
# === 4. Enabled everywhere: every language NOT listed in this function (python, go, java, ...) ===
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"lint": {
|
||||
"rules": {
|
||||
"tags": ["recommended"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { add } from "./util.ts";
|
||||
|
||||
export class Calculator {
|
||||
sum(a: number, b: number): number {
|
||||
return add(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
// Uses the Deno global namespace, which the plain TypeScript language server does not know about.
|
||||
export function describeCwd(): string {
|
||||
return `cwd: ${Deno.cwd()}`;
|
||||
}
|
||||
|
||||
const calc = new Calculator();
|
||||
console.log(calc.sum(2, 3));
|
||||
@@ -0,0 +1,7 @@
|
||||
export function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
export function subtract(a: number, b: number): number {
|
||||
return a - b;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -0,0 +1,5 @@
|
||||
name = "test_repo"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||
@@ -0,0 +1,14 @@
|
||||
# Do not manually edit this file, it is managed by Gleam.
|
||||
#
|
||||
# This file locks the dependency versions used, to make your build
|
||||
# deterministic and to prevent unexpected versions from being included
|
||||
# in your application.
|
||||
#
|
||||
# You should check this file into your source control repository.
|
||||
|
||||
packages = [
|
||||
{ name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1F543AFBA5D33DA493E6087F4E4C4F20D899411343512686C98A8ABB2963CF22" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||
@@ -0,0 +1,37 @@
|
||||
import utils
|
||||
|
||||
/// A simple calculator type.
|
||||
pub type Calculator {
|
||||
Calculator(name: String)
|
||||
}
|
||||
|
||||
/// Adds two integers and returns the result.
|
||||
pub fn add(a: Int, b: Int) -> Int {
|
||||
a + b
|
||||
}
|
||||
|
||||
/// Subtracts b from a.
|
||||
pub fn subtract(a: Int, b: Int) -> Int {
|
||||
a - b
|
||||
}
|
||||
|
||||
/// Multiplies two integers.
|
||||
pub fn multiply(a: Int, b: Int) -> Int {
|
||||
a * b
|
||||
}
|
||||
|
||||
/// Formats the result of an operation using the utils module (cross-file reference).
|
||||
pub fn format_result(label: String, value: Int) -> String {
|
||||
utils.format_output(label, value)
|
||||
}
|
||||
|
||||
/// Returns the description of a calculator.
|
||||
pub fn describe(calc: Calculator) -> String {
|
||||
"Calculator: " <> calc.name
|
||||
}
|
||||
|
||||
/// Demonstrates calling add (same-file reference) and format_output (cross-file reference).
|
||||
pub fn demo() -> String {
|
||||
let sum = add(7, 8)
|
||||
utils.format_output("sum", sum)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// This file intentionally contains a semantic error: `undefined_symbol` is not
|
||||
// defined anywhere, so the Gleam language server reports a diagnostic for it.
|
||||
pub fn broken() -> Int {
|
||||
undefined_symbol
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import gleam/int
|
||||
import gleam/string
|
||||
|
||||
/// Formats a label and integer value into a human-readable string.
|
||||
pub fn format_output(label: String, value: Int) -> String {
|
||||
label <> ": " <> int.to_string(value)
|
||||
}
|
||||
|
||||
/// Checks whether a string is non-empty.
|
||||
pub fn is_non_empty(s: String) -> Bool {
|
||||
!string.is_empty(s)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
work/
|
||||
.nextflow/
|
||||
.nextflow.log*
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env nextflow
|
||||
|
||||
include { GREET; SHOUT } from './modules/greet/main.nf'
|
||||
include { buildGreeting } from './modules/util/main.nf'
|
||||
|
||||
params.names = ['Ada', 'Grace']
|
||||
|
||||
workflow SAY_HELLO {
|
||||
take:
|
||||
names
|
||||
|
||||
main:
|
||||
greetings = GREET(names)
|
||||
shouted = SHOUT(greetings)
|
||||
|
||||
emit:
|
||||
shouted
|
||||
}
|
||||
|
||||
workflow {
|
||||
log.info buildGreeting('World')
|
||||
SAY_HELLO(Channel.fromList(params.names))
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
process GREET {
|
||||
tag "$name"
|
||||
|
||||
input:
|
||||
val name
|
||||
|
||||
output:
|
||||
path "greeting.txt"
|
||||
|
||||
script:
|
||||
"""
|
||||
echo "Hello, ${name}!" > greeting.txt
|
||||
"""
|
||||
}
|
||||
|
||||
process SHOUT {
|
||||
input:
|
||||
path greeting
|
||||
|
||||
output:
|
||||
path "shout.txt"
|
||||
|
||||
script:
|
||||
"""
|
||||
tr '[:lower:]' '[:upper:]' < ${greeting} > shout.txt
|
||||
"""
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
def normalizeName(String name) {
|
||||
return name.trim().toLowerCase()
|
||||
}
|
||||
|
||||
def buildGreeting(String name) {
|
||||
return "Hello, " + normalizeName(name)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
manifest {
|
||||
name = 'serena-nextflow-test-repo'
|
||||
description = 'Minimal Nextflow pipeline used by the solidlsp Nextflow language server tests'
|
||||
}
|
||||
|
||||
process {
|
||||
cpus = 1
|
||||
}
|
||||
+103
-559
@@ -8,16 +8,12 @@
|
||||
"name": "test-repo",
|
||||
"version": "0.0.1",
|
||||
"devDependencies": {
|
||||
"@fontsource/fira-mono": "^5.2.7",
|
||||
"@neoconfetti/svelte": "^2.2.2",
|
||||
"@sveltejs/adapter-auto": "^7.0.1",
|
||||
"@sveltejs/kit": "^2.57.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^25.6.1",
|
||||
"svelte": "^5.55.2",
|
||||
"svelte-check": "^4.4.6",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^6.0.2",
|
||||
"vite": "^8.0.7"
|
||||
}
|
||||
@@ -56,16 +52,6 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/fira-mono": {
|
||||
"version": "5.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-5.2.7.tgz",
|
||||
"integrity": "sha512-wYrAn6i3nH6luqQBZxtWUpl4UTUvs9AEbEeZxksPMwIqyjRRaxHTNW3c2VfM50gabS2IS7pT8lVWS2USB4ukYA==",
|
||||
"dev": true,
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
@@ -135,20 +121,10 @@
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@neoconfetti/svelte": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@neoconfetti/svelte/-/svelte-2.2.2.tgz",
|
||||
"integrity": "sha512-E7xCFVEEm5Ctnj2udTJy1b9oaTvjz1zi1mYdEtE8rB5BVwq6kHisosDS+zdWN5PMfEMjtbsOV9Cl6tsNSAD1sA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.138.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz",
|
||||
"integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==",
|
||||
"version": "0.139.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
|
||||
"integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -163,9 +139,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -180,9 +156,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -197,9 +173,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz",
|
||||
"integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -214,9 +190,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz",
|
||||
"integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -231,9 +207,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz",
|
||||
"integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
|
||||
"integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -248,16 +224,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -268,16 +241,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz",
|
||||
"integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -288,16 +258,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -308,16 +275,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -328,16 +292,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -348,16 +309,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz",
|
||||
"integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -368,9 +326,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -385,9 +343,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz",
|
||||
"integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
|
||||
"integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -404,9 +362,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz",
|
||||
"integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -421,9 +379,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz",
|
||||
"integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -452,9 +410,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sveltejs/acorn-typescript": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz",
|
||||
"integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz",
|
||||
"integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
@@ -472,9 +430,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/kit": {
|
||||
"version": "2.69.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.69.1.tgz",
|
||||
"integrity": "sha512-+nz8Fx/cElzb2ZPHC+6Ll3y3NEVIe4Na75PeplLlyTmd1dBXAjz2KR14y1ZgNjb2ThfAYzulu+PFy1UE3RCUzA==",
|
||||
"version": "2.69.3",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.69.3.tgz",
|
||||
"integrity": "sha512-cphwqMRcE19/9VkrIPr5qZhQ0SptSSDfDzRUpYHu9OJDFGuYBFyJzK+KQA27wB4YG32O/yF2QjBkDmTyo0vtCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -524,9 +482,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.1.2.tgz",
|
||||
"integrity": "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.2.0.tgz",
|
||||
"integrity": "sha512-1SpkuMSRLfugrVX+IrKfE1RUegzo8AQzKQ6qQPfVzbcWi5IhuTPaKb5ZrLpucleFznkc4/RTeSPoRnGWFxX+EQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -543,356 +501,6 @@
|
||||
"vite": "^8.0.0-beta.7 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz",
|
||||
"integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"enhanced-resolve": "5.21.6",
|
||||
"jiti": "^2.7.0",
|
||||
"lightningcss": "1.32.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"source-map-js": "^1.2.1",
|
||||
"tailwindcss": "4.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz",
|
||||
"integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.3.2",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.3.2",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.3.2",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.3.2",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.3.2",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.3.2",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.3.2",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.3.2",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.3.2",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.3.2",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz",
|
||||
"integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz",
|
||||
"integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz",
|
||||
"integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz",
|
||||
"integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz",
|
||||
"integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz",
|
||||
"integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz",
|
||||
"integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz",
|
||||
"integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz",
|
||||
"integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz",
|
||||
"integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==",
|
||||
"bundleDependencies": [
|
||||
"@napi-rs/wasm-runtime",
|
||||
"@emnapi/core",
|
||||
"@emnapi/runtime",
|
||||
"@tybys/wasm-util",
|
||||
"@emnapi/wasi-threads",
|
||||
"tslib"
|
||||
],
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.11.1",
|
||||
"@emnapi/runtime": "^1.11.1",
|
||||
"@emnapi/wasi-threads": "^1.2.2",
|
||||
"@napi-rs/wasm-runtime": "^1.1.4",
|
||||
"@tybys/wasm-util": "^0.10.2",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.11.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.2",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.2",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz",
|
||||
"integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz",
|
||||
"integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz",
|
||||
"integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "4.3.2",
|
||||
"@tailwindcss/oxide": "4.3.2",
|
||||
"tailwindcss": "4.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
||||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
|
||||
@@ -919,9 +527,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.4.tgz",
|
||||
"integrity": "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==",
|
||||
"version": "25.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz",
|
||||
"integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1031,20 +639,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.21.6",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz",
|
||||
"integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esm-env": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||
@@ -1103,13 +697,6 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/is-reference": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
@@ -1120,16 +707,6 @@
|
||||
"@types/estree": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
||||
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
||||
@@ -1283,9 +860,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1307,9 +881,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1331,9 +902,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1355,9 +923,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1451,9 +1016,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.15",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
|
||||
"integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1470,9 +1035,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
|
||||
"integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
|
||||
"integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
@@ -1504,9 +1069,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
|
||||
"integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
|
||||
"version": "8.5.19",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
|
||||
"integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1547,13 +1112,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz",
|
||||
"integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
||||
"integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.138.0",
|
||||
"@oxc-project/types": "=0.139.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -1563,21 +1128,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.1.4",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.4",
|
||||
"@rolldown/binding-darwin-x64": "1.1.4",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.4",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.4",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.4",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.4",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.4",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.4",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.4",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.4"
|
||||
"@rolldown/binding-android-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-x64": "1.1.5",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.5",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.5",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.5",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.5",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.5",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.5",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/sade": {
|
||||
@@ -1594,9 +1159,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.1.tgz",
|
||||
"integrity": "sha512-vM9SUhjsUYs6UeJUmygc5Ofm5eQGe85riob5ju6XCgFGJI5PLV4nrDAQpQjd+LkFBpAkADn5BQQpZ9EUNkyLuA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz",
|
||||
"integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1626,9 +1191,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.56.4",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.4.tgz",
|
||||
"integrity": "sha512-/d0QHehmRuJW8gVz395MTkPcPozxzdjBMBE8oEYGz8O3b9KTMzzQ9ZHJQLuFKOHOPQbU6kx/X4iid/EBBzH7iw==",
|
||||
"version": "5.56.6",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.6.tgz",
|
||||
"integrity": "sha512-p4HDLDogGHKRKCrgckQHNs5PEfXkju6JI5jTywueaKJI5hAdjPohEhRtQ0M1SWC/+TA73SPln+r7srr+7e4nZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1654,9 +1219,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.1.tgz",
|
||||
"integrity": "sha512-FGUOmAqxXdN/H9Zm8slrqO7SLtFisXRB7rfOsHNJ3MLTD2po/+Stg8XyErkpumPHbuUiYTcqrEIzxpVWKTLqtg==",
|
||||
"version": "4.7.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.3.tgz",
|
||||
"integrity": "sha512-DHdTCGX62R0fCxBEaT+USdASAnoaRBaaNczkRJl0K7o3WyoCeVUbVxo6fKqpOll/B+WMWCsiFK0eFrJSNBKZIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1678,27 +1243,6 @@
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz",
|
||||
"integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
||||
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
@@ -1756,16 +1300,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
|
||||
"integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
|
||||
"version": "8.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz",
|
||||
"integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.16",
|
||||
"rolldown": "~1.1.3",
|
||||
"picomatch": "^4.0.5",
|
||||
"postcss": "^8.5.17",
|
||||
"rolldown": "~1.1.5",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
|
||||
@@ -12,16 +12,12 @@
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/fira-mono": "^5.2.7",
|
||||
"@neoconfetti/svelte": "^2.2.2",
|
||||
"@sveltejs/adapter-auto": "^7.0.1",
|
||||
"@sveltejs/kit": "^2.57.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^25.6.1",
|
||||
"svelte": "^5.55.2",
|
||||
"svelte-check": "^4.4.6",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^6.0.2",
|
||||
"vite": "^8.0.7"
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { resolve } from '$app/paths';
|
||||
import { confetti } from '@neoconfetti/svelte';
|
||||
import { MediaQuery } from 'svelte/reactivity';
|
||||
import { words } from '$lib/components/Words.svelte';
|
||||
import { GAME_VERSION } from '$lib/game';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)');
|
||||
|
||||
let shake = $state(false);
|
||||
|
||||
let won = $derived(data.answers.at(-1) === 'xxxxx');
|
||||
@@ -166,19 +162,6 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{#if won}
|
||||
<div
|
||||
style="position: absolute; left: 50%; top: 30%"
|
||||
use:confetti={{
|
||||
particleCount: reducedMotion.current ? 0 : undefined,
|
||||
force: 0.7,
|
||||
stageWidth: window.innerWidth,
|
||||
stageHeight: window.innerHeight,
|
||||
colors: ['#ff3e00', '#40b3ff', '#676778']
|
||||
}}
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
form {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@fontsource/fira-mono';
|
||||
|
||||
:root {
|
||||
--font-body: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({ plugins: [tailwindcss(), sveltekit()] });
|
||||
export default defineConfig({ plugins: [sveltekit()] });
|
||||
@@ -0,0 +1,199 @@
|
||||
import threading
|
||||
from collections.abc import Iterator
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from concurrent.futures import TimeoutError as FutureTimeoutError
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.project_server import ProjectServer, QueryProjectRequest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project_server() -> ProjectServer:
|
||||
server = ProjectServer.__new__(ProjectServer)
|
||||
server._agent = MagicMock()
|
||||
server._loaded_projects_by_root = {}
|
||||
server._project_load_locks_by_root = {}
|
||||
server._active_project_lock = threading.Lock()
|
||||
server._loaded_projects_lock = threading.Lock()
|
||||
return server
|
||||
|
||||
|
||||
def test_cached_project_lookup_is_not_blocked_by_unrelated_cold_load(project_server: ProjectServer) -> None:
|
||||
cached_root = Path("/cached")
|
||||
cold_root = Path("/cold")
|
||||
cached_project = MagicMock()
|
||||
cold_project = MagicMock()
|
||||
cold_load_started = threading.Event()
|
||||
allow_cold_load_to_finish = threading.Event()
|
||||
|
||||
cached_registration = MagicMock(project_root=cached_root)
|
||||
cold_registration = MagicMock(project_root=cold_root)
|
||||
|
||||
def block_cold_load() -> None:
|
||||
cold_load_started.set()
|
||||
assert allow_cold_load_to_finish.wait(timeout=5)
|
||||
|
||||
cold_registration.get_project_instance.return_value = cold_project
|
||||
cold_project.create_language_server_manager.side_effect = block_cold_load
|
||||
project_server._loaded_projects_by_root[str(cached_root)] = cached_project
|
||||
serena_config = cast(Any, project_server._agent.serena_config)
|
||||
serena_config.get_registered_project.side_effect = {
|
||||
"cached": cached_registration,
|
||||
"cold": cold_registration,
|
||||
}.get
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
cold_future = executor.submit(project_server._get_project, "cold")
|
||||
assert cold_load_started.wait(timeout=1)
|
||||
|
||||
cached_future = executor.submit(project_server._get_project, "cached")
|
||||
try:
|
||||
assert cached_future.result(timeout=1) is cached_project
|
||||
finally:
|
||||
allow_cold_load_to_finish.set()
|
||||
|
||||
assert cold_future.result(timeout=1) is cold_project
|
||||
|
||||
|
||||
def test_cold_loads_for_different_projects_can_run_concurrently(project_server: ProjectServer) -> None:
|
||||
first_root = Path("/first")
|
||||
second_root = Path("/second")
|
||||
first_project = MagicMock()
|
||||
second_project = MagicMock()
|
||||
first_load_started = threading.Event()
|
||||
allow_first_load_to_finish = threading.Event()
|
||||
|
||||
first_registration = MagicMock(project_root=first_root)
|
||||
second_registration = MagicMock(project_root=second_root)
|
||||
|
||||
def block_first_load() -> None:
|
||||
first_load_started.set()
|
||||
assert allow_first_load_to_finish.wait(timeout=5)
|
||||
|
||||
first_registration.get_project_instance.return_value = first_project
|
||||
first_project.create_language_server_manager.side_effect = block_first_load
|
||||
second_registration.get_project_instance.return_value = second_project
|
||||
serena_config = cast(Any, project_server._agent.serena_config)
|
||||
serena_config.get_registered_project.side_effect = {
|
||||
"first": first_registration,
|
||||
"second": second_registration,
|
||||
}.get
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
first_future = executor.submit(project_server._get_project, "first")
|
||||
assert first_load_started.wait(timeout=1)
|
||||
|
||||
second_future = executor.submit(project_server._get_project, "second")
|
||||
try:
|
||||
assert second_future.result(timeout=1) is second_project
|
||||
finally:
|
||||
allow_first_load_to_finish.set()
|
||||
|
||||
assert first_future.result(timeout=1) is first_project
|
||||
|
||||
|
||||
def test_concurrent_lookups_load_each_project_only_once(project_server: ProjectServer) -> None:
|
||||
project_root = Path("/project")
|
||||
project = MagicMock()
|
||||
load_started = threading.Event()
|
||||
second_lookup_started = threading.Event()
|
||||
allow_load_to_finish = threading.Event()
|
||||
registration = MagicMock(project_root=project_root)
|
||||
lookup_count = 0
|
||||
lookup_count_lock = threading.Lock()
|
||||
|
||||
def block_load() -> None:
|
||||
load_started.set()
|
||||
assert allow_load_to_finish.wait(timeout=5)
|
||||
|
||||
registration.get_project_instance.return_value = project
|
||||
project.create_language_server_manager.side_effect = block_load
|
||||
serena_config = cast(Any, project_server._agent.serena_config)
|
||||
|
||||
def get_registration(project_name: str) -> MagicMock:
|
||||
nonlocal lookup_count
|
||||
assert project_name == "project"
|
||||
with lookup_count_lock:
|
||||
lookup_count += 1
|
||||
if lookup_count == 2:
|
||||
second_lookup_started.set()
|
||||
return registration
|
||||
|
||||
serena_config.get_registered_project.side_effect = get_registration
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
first_future = executor.submit(project_server._get_project, "project")
|
||||
assert load_started.wait(timeout=1)
|
||||
second_future = executor.submit(project_server._get_project, "project")
|
||||
assert second_lookup_started.wait(timeout=1)
|
||||
allow_load_to_finish.set()
|
||||
|
||||
assert first_future.result(timeout=1) is project
|
||||
assert second_future.result(timeout=1) is project
|
||||
|
||||
registration.get_project_instance.assert_called_once_with(serena_config)
|
||||
project.create_language_server_manager.assert_called_once_with()
|
||||
|
||||
|
||||
def test_concurrent_queries_serialize_active_project_context(project_server: ProjectServer) -> None:
|
||||
first_query_started = threading.Event()
|
||||
second_lookup_finished = threading.Event()
|
||||
allow_first_query_to_finish = threading.Event()
|
||||
state_lock = threading.Lock()
|
||||
state: dict[str, str | None] = {"active_project": None}
|
||||
|
||||
def get_project(project_name: str) -> str:
|
||||
if project_name == "second":
|
||||
second_lookup_finished.set()
|
||||
return project_name
|
||||
|
||||
@contextmanager
|
||||
def active_project_context(project: str) -> Iterator[None]:
|
||||
with state_lock:
|
||||
assert state["active_project"] is None
|
||||
state["active_project"] = project
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
with state_lock:
|
||||
state["active_project"] = None
|
||||
|
||||
def apply_tool(hold: bool = False) -> str:
|
||||
if hold:
|
||||
first_query_started.set()
|
||||
assert allow_first_query_to_finish.wait(timeout=5)
|
||||
with state_lock:
|
||||
active_project = state["active_project"]
|
||||
assert active_project is not None
|
||||
return active_project
|
||||
|
||||
tool = MagicMock()
|
||||
tool.is_readonly.return_value = True
|
||||
tool.apply_ex.side_effect = apply_tool
|
||||
server = cast(Any, project_server)
|
||||
server._get_project = MagicMock(side_effect=get_project)
|
||||
agent = cast(Any, project_server._agent)
|
||||
agent.active_project_context.side_effect = active_project_context
|
||||
agent.get_tool_by_name.return_value = tool
|
||||
first_request = QueryProjectRequest(project_name="first", tool_name="read_file", tool_params_json='{"hold": true}')
|
||||
second_request = QueryProjectRequest(project_name="second", tool_name="read_file", tool_params_json="{}")
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
first_future = executor.submit(project_server._query_project, first_request)
|
||||
assert first_query_started.wait(timeout=1)
|
||||
second_future = executor.submit(project_server._query_project, second_request)
|
||||
assert second_lookup_finished.wait(timeout=1)
|
||||
|
||||
try:
|
||||
with pytest.raises(FutureTimeoutError):
|
||||
second_future.result(timeout=0.1)
|
||||
finally:
|
||||
allow_first_query_to_finish.set()
|
||||
|
||||
assert first_future.result(timeout=1) == "first"
|
||||
assert second_future.result(timeout=1) == "second"
|
||||
@@ -0,0 +1,89 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_types import Hover
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import language_server_tests_enabled
|
||||
|
||||
|
||||
def _hover_text(hover: Hover | None) -> str:
|
||||
"""Flatten the hover contents (markup object, plain string or list of either) into one string."""
|
||||
assert hover is not None, "Expected hover information, got None"
|
||||
contents = hover["contents"]
|
||||
items = contents if isinstance(contents, list) else [contents]
|
||||
return "\n".join(item if isinstance(item, str) else item["value"] for item in items)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.DENO), reason="Deno tests are disabled (deno not available)")
|
||||
@pytest.mark.deno
|
||||
class TestDenoLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.DENO], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.DENO], indirect=True)
|
||||
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
assert language_server.is_running()
|
||||
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.DENO], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.DENO], indirect=True)
|
||||
def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
# main.ts line 4 (0-indexed): " return add(a, b);" — cursor on `add` (char 11).
|
||||
# `add` is defined in util.ts at line 0.
|
||||
definitions = language_server.request_definition(str(repo_path / "main.ts"), 4, 11)
|
||||
|
||||
assert definitions, f"Expected a definition for `add`, got {definitions=}"
|
||||
definition = definitions[0]
|
||||
assert definition["uri"].endswith("util.ts")
|
||||
assert definition["range"]["start"]["line"] == 0
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.DENO], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.DENO], indirect=True)
|
||||
def test_find_references_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
# `add` is defined in util.ts line 0 at char len("export function ") == 16.
|
||||
references = language_server.request_references(str(repo_path / "util.ts"), 0, len("export function "))
|
||||
|
||||
assert references, f"Expected references for `add`, got {references=}"
|
||||
locations = [(ref["uri"].split("/")[-1], ref["range"]["start"]["line"]) for ref in references]
|
||||
# The usage inside Calculator.sum lives on line 4 of main.ts.
|
||||
assert ("main.ts", 4) in locations, f"Expected a reference in main.ts line 4, got {locations}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.DENO], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.DENO], indirect=True)
|
||||
def test_document_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
all_symbols, _roots = language_server.request_document_symbols("main.ts").get_all_symbols_and_roots()
|
||||
names = [sym.get("name") for sym in all_symbols]
|
||||
assert "Calculator" in names, f"Calculator not found in main.ts document symbols: {names}"
|
||||
assert "sum" in names, f"sum not found in main.ts document symbols: {names}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.DENO], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.DENO], indirect=True)
|
||||
def test_find_symbol(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add not found in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.DENO], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.DENO], indirect=True)
|
||||
def test_hover_reports_function_signature(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
# `add` is defined in util.ts line 0 at char len("export function ") == 16.
|
||||
hover = language_server.request_hover(str(repo_path / "util.ts"), 0, len("export function "))
|
||||
|
||||
text = _hover_text(hover)
|
||||
assert "add" in text, f"Hover should name the function, got: {text}"
|
||||
assert "(a: number, b: number): number" in text, f"Hover should carry the signature, got: {text}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.DENO], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.DENO], indirect=True)
|
||||
def test_hover_resolves_deno_global(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
"""The `Deno.*` namespace is what this server adds over the plain TypeScript one, so it must resolve."""
|
||||
main = repo_path / "main.ts"
|
||||
lines = main.read_text(encoding="utf-8").splitlines()
|
||||
line = next(i for i, content in enumerate(lines) if "Deno.cwd()" in content)
|
||||
|
||||
hover = language_server.request_hover(str(main), line, lines[line].index("Deno.cwd") + len("Deno."))
|
||||
|
||||
text = _hover_text(hover)
|
||||
assert "cwd" in text, f"Hover should resolve Deno.cwd, got: {text}"
|
||||
assert "string" in text, f"Hover should carry Deno.cwd's return type, got: {text}"
|
||||
Whitespace-only changes.
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
Basic integration tests for the Gleam language server.
|
||||
|
||||
These tests validate startup, document symbols, same-file and cross-file
|
||||
reference search, and diagnostics, using the Gleam test repository at
|
||||
``test/resources/repos/gleam/test_repo``.
|
||||
|
||||
Requires the ``gleam`` compiler on PATH (the language server is bundled with it
|
||||
and started via ``gleam lsp``).
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.util.text_utils import find_text_coordinates
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.conftest import read_repo_file
|
||||
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.gleam,
|
||||
pytest.mark.skipif(
|
||||
not language_server_tests_enabled(LanguageServerId.GLEAM),
|
||||
reason="Gleam tests are disabled (gleam compiler not available)",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class TestGleamLanguageServer:
|
||||
"""Test Gleam language server startup and basic features."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GLEAM], indirect=True)
|
||||
def test_ls_is_running(self, language_server: SolidLanguageServer) -> None:
|
||||
"""The language server starts successfully."""
|
||||
assert language_server.is_running()
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GLEAM], indirect=True)
|
||||
def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Document symbols for calculator.gleam include the public functions and the type."""
|
||||
file_path = os.path.join("src", "calculator.gleam")
|
||||
doc_symbols = language_server.request_document_symbols(file_path)
|
||||
all_symbols, _ = doc_symbols.get_all_symbols_and_roots()
|
||||
|
||||
names = {s.get("name") for s in all_symbols if s.get("name")}
|
||||
assert "add" in names, f"'add' missing from calculator.gleam symbols: {names}"
|
||||
assert "subtract" in names, f"'subtract' missing: {names}"
|
||||
assert "multiply" in names, f"'multiply' missing: {names}"
|
||||
assert "Calculator" in names, f"'Calculator' type missing: {names}"
|
||||
assert "demo" in names, f"'demo' missing: {names}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GLEAM], indirect=True)
|
||||
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
|
||||
"""The full project symbol tree includes symbols from both modules.
|
||||
|
||||
``find_symbol`` (serena's most-used tool) is backed by ``request_full_symbol_tree``;
|
||||
verify it sees public symbols from calculator.gleam and utils.gleam across files.
|
||||
"""
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
# calculator.gleam symbols
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "calculator 'add' missing from symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator type missing from symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "demo"), "calculator 'demo' missing from symbol tree"
|
||||
# utils.gleam symbols (cross-file)
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "format_output"), "utils 'format_output' missing from symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "is_non_empty"), "utils 'is_non_empty' missing from symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GLEAM], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.GLEAM], indirect=True)
|
||||
def test_find_references_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
"""References to ``add`` include its call site in ``demo`` (same file).
|
||||
|
||||
``add`` is defined in calculator.gleam and called once in ``demo``; both the
|
||||
declaration and the call site should be returned by ``request_references``.
|
||||
"""
|
||||
rel = os.path.join("src", "calculator.gleam")
|
||||
file_path = str(repo_path / rel)
|
||||
content = read_repo_file(language_server, rel)
|
||||
|
||||
coords = find_text_coordinates(content, r"pub fn (add)\(")
|
||||
assert coords is not None, "Could not locate `pub fn add` definition in calculator.gleam"
|
||||
|
||||
references = language_server.request_references(file_path, coords.line, coords.col + 1)
|
||||
assert references, f"Expected non-empty references for add, got {references=}"
|
||||
|
||||
ref_files = {loc["uri"].split("/")[-1] for loc in references}
|
||||
assert "calculator.gleam" in ref_files, f"Expected reference in calculator.gleam, got {ref_files}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GLEAM], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.GLEAM], indirect=True)
|
||||
def test_find_references_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
"""Cross-file references: ``format_output`` (defined in utils.gleam) is called in calculator.gleam."""
|
||||
rel = os.path.join("src", "utils.gleam")
|
||||
file_path = str(repo_path / rel)
|
||||
content = read_repo_file(language_server, rel)
|
||||
|
||||
coords = find_text_coordinates(content, r"pub fn (format_output)\(")
|
||||
assert coords is not None, "Could not locate `pub fn format_output` definition in utils.gleam"
|
||||
|
||||
references = language_server.request_references(file_path, coords.line, coords.col + 1)
|
||||
assert references, f"Expected non-empty references for format_output, got {references=}"
|
||||
|
||||
ref_files = {loc["uri"].split("/")[-1] for loc in references}
|
||||
assert "calculator.gleam" in ref_files, f"Expected cross-file reference in calculator.gleam, got {ref_files}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GLEAM], indirect=True)
|
||||
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
|
||||
"""diagnostics_sample.gleam references an undefined symbol; the LSP should report it."""
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
os.path.join("src", "diagnostics_sample.gleam"),
|
||||
(),
|
||||
min_count=1,
|
||||
)
|
||||
@@ -455,7 +455,7 @@ class TestSetupFromExistingInstall:
|
||||
(jdk / "bin" / "java").touch()
|
||||
return jdk
|
||||
|
||||
def test_happy_path_returns_runtime_paths_with_no_gradle_and_no_intellicode(
|
||||
def test_happy_path_returns_runtime_paths_with_no_gradle(
|
||||
self, tmp_path: Path, jdtls_root: Path, lombok_jar: Path, custom_settings: SolidLSPSettings.CustomLSSettings
|
||||
) -> None:
|
||||
jdk = self._fake_jdk(tmp_path)
|
||||
@@ -465,8 +465,6 @@ class TestSetupFromExistingInstall:
|
||||
result = EclipseJDTLS.DependencyProvider._setup_from_existing_install(str(jdtls_root), str(lombok_jar), custom_settings)
|
||||
|
||||
assert result.gradle_path is None
|
||||
assert result.intellicode_jar_path is None
|
||||
assert result.intellisense_members_path is None
|
||||
assert result.lombok_jar_path == str(lombok_jar)
|
||||
assert result.jre_home_path == str(jdk)
|
||||
assert Path(result.jdtls_launcher_jar_path).name.startswith("org.eclipse.equinox.launcher_")
|
||||
|
||||
@@ -10,7 +10,7 @@ from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
# Kotlin LSP (IntelliJ-based, pre-alpha v261) crashes on JVM restart under CI resource constraints
|
||||
# Kotlin LSP (IntelliJ-based, pre-alpha v262) crashes on JVM restart under CI resource constraints
|
||||
# (2 CPUs, 7GB RAM). First start succeeds but subsequent starts fail with cancelled (-32800).
|
||||
# Tests pass reliably on developer machines. See PR #1061 for investigation details.
|
||||
# (The CI quarantine lives centrally in test/conftest.py::_determine_disabled_languages.)
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
"""Tests for Kotlin Language Server dependency resolution and installation."""
|
||||
|
||||
from contextlib import nullcontext
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.dependency_provider import DownloadedDependency, DownloadedDependencyHashDatabase
|
||||
from solidlsp.language_servers.kotlin_language_server import (
|
||||
DEFAULT_KOTLIN_LSP_VERSION,
|
||||
INITIAL_KOTLIN_LSP_VERSION,
|
||||
KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
KotlinLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_utils import PlatformId
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
def _make_provider(
|
||||
tmp_path: Path,
|
||||
custom_settings: dict[str, str] | None = None,
|
||||
) -> KotlinLanguageServer.DependencyProvider:
|
||||
return KotlinLanguageServer.DependencyProvider(
|
||||
custom_settings=SolidLSPSettings.CustomLSSettings(custom_settings or {}),
|
||||
ls_resources_dir=str(tmp_path),
|
||||
project_cache_dir=str(tmp_path / "project-cache"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.kotlin
|
||||
class TestKotlinDependencyProvider:
|
||||
@pytest.mark.parametrize(
|
||||
("platform_id", "asset_suffix", "archive_type", "launcher_parts", "sha256"),
|
||||
[
|
||||
(
|
||||
PlatformId.WIN_x64,
|
||||
".win.zip",
|
||||
"zip",
|
||||
("bin", "intellij-server.exe"),
|
||||
"f2daaa476f26d99301b406f76de6d87c437d04dc72f06845154619d8f991c51f",
|
||||
),
|
||||
(
|
||||
PlatformId.WIN_arm64,
|
||||
"-aarch64.win.zip",
|
||||
"zip",
|
||||
("bin", "intellij-server.exe"),
|
||||
"73a552a6a420158622e5ad8d96b53da8aa8ced3f88a24fded01575927a2fd8e7",
|
||||
),
|
||||
(
|
||||
PlatformId.LINUX_x64,
|
||||
".tar.gz",
|
||||
"gztar",
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
"2d99d8e198fbe4aa8f4481e37799724ce94803b4ea12a60b416040e3fcd7cc5e",
|
||||
),
|
||||
(
|
||||
PlatformId.LINUX_arm64,
|
||||
"-aarch64.tar.gz",
|
||||
"gztar",
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
"2317831c6e5607d05b7ebc1da655330125ce0e3d66fbf24517dfce442debc14e",
|
||||
),
|
||||
(
|
||||
PlatformId.OSX_x64,
|
||||
".sit",
|
||||
"zip",
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
"17369fda97c85418ac24ab38a9df56b21522a3468dfe193832fe455c13920745",
|
||||
),
|
||||
(
|
||||
PlatformId.OSX_arm64,
|
||||
"-aarch64.sit",
|
||||
"zip",
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
"6ba6021a706b21e64cef33f7e2b79f187c0910320722bb2d3ed05ad1115ec43f",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_default_artifacts_match_jetbrains_release_matrix(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
platform_id: PlatformId,
|
||||
asset_suffix: str,
|
||||
archive_type: str,
|
||||
launcher_parts: tuple[str, ...],
|
||||
sha256: str,
|
||||
) -> None:
|
||||
artifact = KotlinLanguageServer.DependencyProvider._create_artifact(DEFAULT_KOTLIN_LSP_VERSION, platform_id)
|
||||
url = (
|
||||
f"https://download-cdn.jetbrains.com/language-server/kotlin-server/{DEFAULT_KOTLIN_LSP_VERSION}/"
|
||||
f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}{asset_suffix}"
|
||||
)
|
||||
|
||||
with patch("solidlsp.dependency_provider.FileUtils.download_and_extract_archive_verified") as download:
|
||||
artifact.dependency.download_to(tmp_path)
|
||||
|
||||
download.assert_called_once_with(
|
||||
url,
|
||||
str(tmp_path),
|
||||
archive_type=archive_type,
|
||||
expected_sha256=sha256,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
)
|
||||
assert artifact.launcher_parts == launcher_parts
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("version", "platform_id", "url", "launcher_parts"),
|
||||
[
|
||||
(
|
||||
"262.2310.0",
|
||||
PlatformId.LINUX_x64,
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/262.2310.0/kotlin-lsp-262.2310.0-linux-x64.zip",
|
||||
("kotlin-lsp.sh",),
|
||||
),
|
||||
(
|
||||
"262.4739.0",
|
||||
PlatformId.LINUX_x64,
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/262.4739.0/kotlin-server-262.4739.0.tar.gz",
|
||||
("kotlin-server-262.4739.0", "bin", "intellij-server"),
|
||||
),
|
||||
(
|
||||
"262.8190.0",
|
||||
PlatformId.OSX_arm64,
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.8190.0/kotlin-server-262.8190.0-aarch64.sit",
|
||||
("kotlin-server-262.8190.0", "bin", "intellij-server"),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_version_boundaries_select_the_published_layout(
|
||||
self,
|
||||
version: str,
|
||||
platform_id: PlatformId,
|
||||
url: str,
|
||||
launcher_parts: tuple[str, ...],
|
||||
) -> None:
|
||||
artifact = KotlinLanguageServer.DependencyProvider._create_artifact(version, platform_id)
|
||||
|
||||
assert artifact.dependency.get_url() == url
|
||||
assert artifact.launcher_parts == launcher_parts
|
||||
|
||||
def test_custom_versions_skip_hash_lookup_by_design(self, tmp_path: Path) -> None:
|
||||
artifact = KotlinLanguageServer.DependencyProvider._create_artifact("262.8190.1", PlatformId.LINUX_x64)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"solidlsp.dependency_provider.DownloadedDependencyHashDatabase.get_instance",
|
||||
side_effect=AssertionError("custom versions must not consult the pinned hash database"),
|
||||
),
|
||||
patch("solidlsp.dependency_provider.FileUtils.download_and_extract_archive_verified") as download,
|
||||
):
|
||||
artifact.dependency.download_to(tmp_path)
|
||||
|
||||
download.assert_called_once_with(
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.8190.1/kotlin-server-262.8190.1.tar.gz",
|
||||
str(tmp_path),
|
||||
archive_type="gztar",
|
||||
expected_sha256=None,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
def test_hash_updater_registers_frozen_and_default_artifacts(self) -> None:
|
||||
updated_asset_names: list[str] = []
|
||||
|
||||
class RecordingUpdater:
|
||||
def update(self, dependency: DownloadedDependency) -> None:
|
||||
updated_asset_names.append(dependency.get_url().rsplit("/", 1)[-1])
|
||||
|
||||
with patch.object(DownloadedDependencyHashDatabase, "get_instance") as get_hash_database:
|
||||
get_hash_database.return_value.update_context.return_value = nullcontext(RecordingUpdater())
|
||||
KotlinLanguageServer.DependencyProvider.update_dep_hashes()
|
||||
|
||||
assert set(updated_asset_names) == {
|
||||
"kotlin-lsp-261.13587.0-win-x64.zip",
|
||||
"kotlin-lsp-261.13587.0-linux-x64.zip",
|
||||
"kotlin-lsp-261.13587.0-linux-aarch64.zip",
|
||||
"kotlin-lsp-261.13587.0-mac-x64.zip",
|
||||
"kotlin-lsp-261.13587.0-mac-aarch64.zip",
|
||||
"kotlin-server-262.9593.0.win.zip",
|
||||
"kotlin-server-262.9593.0-aarch64.win.zip",
|
||||
"kotlin-server-262.9593.0.tar.gz",
|
||||
"kotlin-server-262.9593.0-aarch64.tar.gz",
|
||||
"kotlin-server-262.9593.0.sit",
|
||||
"kotlin-server-262.9593.0-aarch64.sit",
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("settings", "platform_id", "relative_launcher", "expected_directory"),
|
||||
[
|
||||
(
|
||||
{"kotlin_lsp_version": INITIAL_KOTLIN_LSP_VERSION},
|
||||
PlatformId.LINUX_x64,
|
||||
("kotlin-lsp.sh",),
|
||||
"kotlin_language_server",
|
||||
),
|
||||
(
|
||||
{},
|
||||
PlatformId.LINUX_arm64,
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
f"kotlin_language_server-{DEFAULT_KOTLIN_LSP_VERSION}",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_installs_pinned_versions_in_compatible_directories(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
settings: dict[str, str],
|
||||
platform_id: PlatformId,
|
||||
relative_launcher: tuple[str, ...],
|
||||
expected_directory: str,
|
||||
) -> None:
|
||||
provider = _make_provider(tmp_path, settings)
|
||||
|
||||
def fake_download(
|
||||
_url: str,
|
||||
target_path: str,
|
||||
archive_type: str,
|
||||
expected_sha256: str | None = None,
|
||||
allowed_hosts: tuple[str, ...] | list[str] | None = None,
|
||||
) -> None:
|
||||
del archive_type, expected_sha256, allowed_hosts
|
||||
launcher = Path(target_path).joinpath(*relative_launcher)
|
||||
launcher.parent.mkdir(parents=True, exist_ok=True)
|
||||
launcher.write_text("#!/bin/sh\n", encoding="utf-8")
|
||||
|
||||
with (
|
||||
patch(
|
||||
"solidlsp.language_servers.kotlin_language_server.PlatformUtils.get_platform_id",
|
||||
return_value=platform_id,
|
||||
),
|
||||
patch(
|
||||
"solidlsp.dependency_provider.FileUtils.download_and_extract_archive_verified",
|
||||
side_effect=fake_download,
|
||||
),
|
||||
):
|
||||
launcher_path = provider._get_or_install_core_dependency()
|
||||
|
||||
assert launcher_path == str((tmp_path / expected_directory).joinpath(*relative_launcher))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("platform_id", "modern_launcher", "other_os_launcher"),
|
||||
[
|
||||
(PlatformId.LINUX_x64, "/path/to/intellij-server", "/path/to/intellij-server.exe"),
|
||||
(PlatformId.WIN_x64, "/path/to/intellij-server.exe", "/path/to/intellij-server"),
|
||||
],
|
||||
)
|
||||
def test_custom_launcher_arguments_use_the_active_os_name(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
platform_id: PlatformId,
|
||||
modern_launcher: str,
|
||||
other_os_launcher: str,
|
||||
) -> None:
|
||||
modern_provider = _make_provider(tmp_path, {"ls_path": modern_launcher})
|
||||
other_os_provider = _make_provider(tmp_path, {"ls_path": other_os_launcher})
|
||||
|
||||
with patch(
|
||||
"solidlsp.language_servers.kotlin_language_server.PlatformUtils.get_platform_id",
|
||||
return_value=platform_id,
|
||||
):
|
||||
assert modern_provider.create_launch_command() == [
|
||||
modern_launcher,
|
||||
"--stdio",
|
||||
"--system-path",
|
||||
str(tmp_path / "project-cache" / "kotlin-lsp-system"),
|
||||
]
|
||||
assert other_os_provider.create_launch_command() == [other_os_launcher, "--stdio"]
|
||||
|
||||
def test_invalid_version_is_rejected_before_download(self) -> None:
|
||||
with pytest.raises(ValueError, match="dot-separated integers"):
|
||||
KotlinLanguageServer.DependencyProvider._create_artifact("latest", PlatformId.OSX_arm64)
|
||||
|
||||
def test_unsupported_modern_platform_is_rejected(self) -> None:
|
||||
with pytest.raises(ValueError, match="Unsupported platform"):
|
||||
KotlinLanguageServer.DependencyProvider._create_artifact(DEFAULT_KOTLIN_LSP_VERSION, PlatformId.LINUX_x86)
|
||||
@@ -2,9 +2,11 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
|
||||
@pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.KOTLIN), reason="Kotlin tests are disabled")
|
||||
@pytest.mark.kotlin
|
||||
class TestKotlinDiagnostics:
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.KOTLIN], indirect=True)
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Regression tests for Kotlin LSP startup readiness handling."""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.language_servers.kotlin_language_server import KotlinLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
pytestmark = pytest.mark.kotlin
|
||||
|
||||
_REQUIRED_CAPABILITIES = {
|
||||
"textDocumentSync": 1,
|
||||
"hoverProvider": True,
|
||||
"completionProvider": {},
|
||||
"signatureHelpProvider": {},
|
||||
"definitionProvider": True,
|
||||
"referencesProvider": True,
|
||||
"documentSymbolProvider": True,
|
||||
"workspaceSymbolProvider": True,
|
||||
"semanticTokensProvider": {},
|
||||
}
|
||||
|
||||
|
||||
def _make_server(tmp_path: Path) -> tuple[KotlinLanguageServer, Mock, Mock]:
|
||||
settings = SolidLSPSettings(
|
||||
solidlsp_dir=str(tmp_path / "global"),
|
||||
project_data_path=str(tmp_path / "project"),
|
||||
ls_specific_settings={LanguageServerId.KOTLIN: {}},
|
||||
)
|
||||
server_interface = Mock()
|
||||
with patch.object(KotlinLanguageServer, "_create_language_server_interface", return_value=server_interface):
|
||||
server = KotlinLanguageServer(
|
||||
LanguageServerConfig(ls_id=LanguageServerId.KOTLIN),
|
||||
str(tmp_path),
|
||||
settings,
|
||||
)
|
||||
|
||||
indexing_complete = Mock()
|
||||
indexing_complete.wait.return_value = True
|
||||
intellij_server_ready = Mock()
|
||||
intellij_server_ready.wait.return_value = True
|
||||
server_state = cast(Any, server)
|
||||
server_state._indexing_complete = indexing_complete
|
||||
server_state._intellij_server_ready = intellij_server_ready
|
||||
return server, indexing_complete, intellij_server_ready
|
||||
|
||||
|
||||
def _configure_initialize(server: KotlinLanguageServer, server_name: str | None) -> dict[str, Any]:
|
||||
response: dict[str, Any] = {"capabilities": _REQUIRED_CAPABILITIES}
|
||||
if server_name is not None:
|
||||
response["serverInfo"] = {"name": server_name}
|
||||
language_server_interface = cast(Any, server.server)
|
||||
language_server_interface.send.initialize.return_value = response
|
||||
return response
|
||||
|
||||
|
||||
def test_modern_server_registers_and_waits_for_explicit_ready_notification(tmp_path: Path) -> None:
|
||||
server, indexing_complete, intellij_server_ready = _make_server(tmp_path)
|
||||
_configure_initialize(server, "IntelliJ Language Server by JetBrains")
|
||||
events: list[str] = []
|
||||
notification_handlers: dict[str, Any] = {}
|
||||
language_server_interface = cast(Any, server.server)
|
||||
|
||||
def on_notification(method: str, handler: Any) -> None:
|
||||
events.append(f"notification:{method}")
|
||||
notification_handlers[method] = handler
|
||||
|
||||
language_server_interface.on_notification.side_effect = on_notification
|
||||
language_server_interface.start.side_effect = lambda: events.append("start")
|
||||
language_server_interface.send.initialize.side_effect = lambda _params: (
|
||||
events.append("initialize")
|
||||
or {"capabilities": _REQUIRED_CAPABILITIES, "serverInfo": {"name": "IntelliJ Language Server by JetBrains"}}
|
||||
)
|
||||
|
||||
server._start_server()
|
||||
|
||||
assert events.index("notification:intellij/ready-for-test") < events.index("start") < events.index("initialize")
|
||||
intellij_server_ready.clear.assert_called_once_with()
|
||||
intellij_server_ready.wait.assert_called_once_with(timeout=120.0)
|
||||
indexing_complete.wait.assert_not_called()
|
||||
|
||||
notification_handlers["intellij/ready-for-test"](None)
|
||||
intellij_server_ready.set.assert_called_once_with()
|
||||
|
||||
|
||||
def test_legacy_server_keeps_progress_event_wait(tmp_path: Path) -> None:
|
||||
server, indexing_complete, intellij_server_ready = _make_server(tmp_path)
|
||||
_configure_initialize(server, None)
|
||||
|
||||
server._start_server()
|
||||
|
||||
indexing_complete.wait.assert_called_once_with(timeout=120.0)
|
||||
intellij_server_ready.wait.assert_not_called()
|
||||
|
||||
|
||||
def test_modern_ready_timeout_warns_and_continues(tmp_path: Path, caplog: Any) -> None:
|
||||
server, _indexing_complete, intellij_server_ready = _make_server(tmp_path)
|
||||
_configure_initialize(server, "IntelliJ Language Server by JetBrains")
|
||||
intellij_server_ready.wait.return_value = False
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
server._start_server()
|
||||
|
||||
assert "Kotlin LSP did not signal indexing completion within 120s; proceeding anyway" in caplog.text
|
||||
@@ -0,0 +1,87 @@
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
|
||||
pytestmark = pytest.mark.skipif(shutil.which("java") is None, reason="Nextflow language server requires a Java runtime")
|
||||
|
||||
|
||||
@pytest.mark.nextflow
|
||||
class TestNextflowLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.NEXTFLOW], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [LanguageServerId.NEXTFLOW], indirect=True)
|
||||
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
|
||||
"""Language server starts and attaches to the test repository."""
|
||||
assert language_server.is_running()
|
||||
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.NEXTFLOW], indirect=True)
|
||||
def test_document_symbols_workflows(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Workflows declared in a script are reported, with the ``workflow`` keyword stripped."""
|
||||
all_symbols, _ = language_server.request_document_symbols("main.nf").get_all_symbols_and_roots()
|
||||
names = [s["name"] for s in all_symbols]
|
||||
assert "SAY_HELLO" in names, f"SAY_HELLO not found in main.nf symbols. Found: {names}"
|
||||
# the implicit entry workflow has no name of its own; the language server calls it "<entry>"
|
||||
assert "<entry>" in names, f"entry workflow not found in main.nf symbols. Found: {names}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.NEXTFLOW], indirect=True)
|
||||
def test_document_symbols_processes_and_functions(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Processes and functions are reported without their declaration keyword."""
|
||||
process_symbols, _ = language_server.request_document_symbols("modules/greet/main.nf").get_all_symbols_and_roots()
|
||||
process_names = [s["name"] for s in process_symbols]
|
||||
assert "GREET" in process_names, f"GREET not found in modules/greet/main.nf symbols. Found: {process_names}"
|
||||
assert "SHOUT" in process_names, f"SHOUT not found in modules/greet/main.nf symbols. Found: {process_names}"
|
||||
|
||||
function_symbols, _ = language_server.request_document_symbols("modules/util/main.nf").get_all_symbols_and_roots()
|
||||
function_names = [s["name"] for s in function_symbols]
|
||||
assert "normalizeName" in function_names, f"normalizeName not found in modules/util/main.nf symbols. Found: {function_names}"
|
||||
assert "buildGreeting" in function_names, f"buildGreeting not found in modules/util/main.nf symbols. Found: {function_names}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.NEXTFLOW], indirect=True)
|
||||
def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
for name in ("SAY_HELLO", "GREET", "SHOUT", "normalizeName", "buildGreeting"):
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, name), f"{name} not found in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.NEXTFLOW], indirect=True)
|
||||
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
|
||||
"""``normalizeName`` is defined and called within modules/util/main.nf only."""
|
||||
util_path = os.path.join("modules", "util", "main.nf")
|
||||
symbol = self._get_symbol(language_server, util_path, "normalizeName")
|
||||
start = symbol["selectionRange"]["start"]
|
||||
|
||||
refs = language_server.request_references(util_path, start["line"], start["character"])
|
||||
ref_lines = self._reference_lines(refs)
|
||||
# modules/util/main.nf, 0-indexed line 5: return "Hello, " + normalizeName(name)
|
||||
assert (util_path, 5) in ref_lines, f"call to normalizeName inside buildGreeting not found. Found: {ref_lines}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.NEXTFLOW], indirect=True)
|
||||
def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
|
||||
"""The GREET process is defined in a module and used by the workflow in main.nf."""
|
||||
module_path = os.path.join("modules", "greet", "main.nf")
|
||||
symbol = self._get_symbol(language_server, module_path, "GREET")
|
||||
start = symbol["selectionRange"]["start"]
|
||||
|
||||
refs = language_server.request_references(module_path, start["line"], start["character"])
|
||||
ref_lines = self._reference_lines(refs)
|
||||
# main.nf, 0-indexed line 2: include { GREET; SHOUT } from './modules/greet/main.nf'
|
||||
assert ("main.nf", 2) in ref_lines, f"include of GREET in main.nf not found. Found: {ref_lines}"
|
||||
# main.nf, 0-indexed line 12: greetings = GREET(names)
|
||||
assert ("main.nf", 12) in ref_lines, f"invocation of GREET in main.nf not found. Found: {ref_lines}"
|
||||
|
||||
@staticmethod
|
||||
def _reference_lines(references: list[dict]) -> set[tuple[str, int]]:
|
||||
"""(relative path, 0-indexed start line) per reference; paths use the platform separator."""
|
||||
return {(ref["relativePath"], ref["range"]["start"]["line"]) for ref in references}
|
||||
|
||||
@staticmethod
|
||||
def _get_symbol(language_server: SolidLanguageServer, relative_path: str, name: str) -> dict:
|
||||
all_symbols, _ = language_server.request_document_symbols(relative_path).get_all_symbols_and_roots()
|
||||
matches = [s for s in all_symbols if s["name"] == name]
|
||||
assert matches, f"{name} not found in {relative_path}. Found: {[s['name'] for s in all_symbols]}"
|
||||
return matches[0]
|
||||
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Unit tests for the detection of Scala build roots, which Metals is given as workspace folders.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import cast
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import url2pathname
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.language_servers.scala_language_server import (
|
||||
DEFAULT_PROJECT_ROOT_SCAN_DEPTH,
|
||||
ScalaInitializeParamsBuilder,
|
||||
ScalaLanguageServer,
|
||||
find_build_roots,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
def make_sbt_build(path: Path) -> Path:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
(path / "build.sbt").write_text('ThisBuild / scalaVersion := "3.3.6"\n')
|
||||
(path / "src" / "main" / "scala").mkdir(parents=True)
|
||||
return path
|
||||
|
||||
|
||||
@pytest.mark.scala
|
||||
class TestFindBuildRoots:
|
||||
def test_repository_root_is_the_build_root(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path)
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path)]
|
||||
|
||||
def test_single_build_below_the_root(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "backend")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "backend")]
|
||||
|
||||
def test_several_builds_below_the_root(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "backend")
|
||||
make_sbt_build(tmp_path / "tooling")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "backend"), str(tmp_path / "tooling")]
|
||||
|
||||
def test_nested_build(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "scala" / "backend")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "scala" / "backend")]
|
||||
|
||||
def test_scan_depth_is_bounded(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "a" / "b" / "c")
|
||||
assert find_build_roots(str(tmp_path), max_depth=2) == [str(tmp_path)]
|
||||
assert find_build_roots(str(tmp_path), max_depth=3) == [str(tmp_path / "a" / "b" / "c")]
|
||||
|
||||
def test_does_not_descend_into_a_build_root(self, tmp_path: Path) -> None:
|
||||
"""A subproject of an sbt build is not a build root of its own."""
|
||||
make_sbt_build(tmp_path / "backend")
|
||||
make_sbt_build(tmp_path / "backend" / "module")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "backend")]
|
||||
|
||||
def test_falls_back_to_the_repository_root(self, tmp_path: Path) -> None:
|
||||
"""With nothing to find, Metals' own behaviour is left unchanged."""
|
||||
(tmp_path / "docs").mkdir()
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path)]
|
||||
|
||||
def test_hidden_and_uninteresting_directories_are_skipped(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / ".git" / "backend")
|
||||
make_sbt_build(tmp_path / "node_modules" / "backend")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path)]
|
||||
|
||||
def test_bsp_connection_file_marks_a_build_root(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "backend" / ".bsp").mkdir(parents=True)
|
||||
(tmp_path / "backend" / ".bsp" / "sbt.json").write_text("{}")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "backend")]
|
||||
|
||||
def test_sbt_build_defined_only_under_project(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "backend" / "project").mkdir(parents=True)
|
||||
(tmp_path / "backend" / "project" / "build.properties").write_text("sbt.version=1.11.7\n")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "backend")]
|
||||
|
||||
def test_build_properties_without_a_version_is_not_a_build_root(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "backend" / "project").mkdir(parents=True)
|
||||
(tmp_path / "backend" / "project" / "build.properties").write_text("# nothing to see here\n")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path)]
|
||||
|
||||
def test_an_empty_bsp_directory_is_not_a_build_root(self, tmp_path: Path) -> None:
|
||||
"""Metals requires a connection file in there, not merely the directory."""
|
||||
(tmp_path / "backend" / ".bsp").mkdir(parents=True)
|
||||
make_sbt_build(tmp_path / "backend" / "module")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "backend" / "module")]
|
||||
|
||||
def test_a_skipped_directory_is_still_probed(self, tmp_path: Path) -> None:
|
||||
"""`src` and friends are not descended into, but may themselves be a build root."""
|
||||
make_sbt_build(tmp_path / "src")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "src")]
|
||||
|
||||
def test_a_skipped_directory_is_not_descended_into(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "node_modules" / "backend")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path)]
|
||||
|
||||
def test_bazel_workspace_marker(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "backend").mkdir()
|
||||
(tmp_path / "backend" / "MODULE.bazel").write_text("")
|
||||
assert find_build_roots(str(tmp_path)) == [str(tmp_path / "backend")]
|
||||
|
||||
def test_symlinked_build_is_found_without_looping(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "elsewhere" / "backend")
|
||||
(tmp_path / "repo").mkdir()
|
||||
(tmp_path / "repo" / "link").symlink_to(tmp_path / "elsewhere", target_is_directory=True)
|
||||
(tmp_path / "repo" / "loop").symlink_to(tmp_path / "repo", target_is_directory=True)
|
||||
assert find_build_roots(str(tmp_path / "repo")) == [str(tmp_path / "repo" / "link" / "backend")]
|
||||
|
||||
|
||||
@pytest.mark.scala
|
||||
class TestResolveBuildRoots:
|
||||
"""The `project_roots` / `project_root_scan_depth` settings, which override the detection."""
|
||||
|
||||
@staticmethod
|
||||
def resolve(root: Path, **scala_settings) -> list[str]:
|
||||
settings = SolidLSPSettings(ls_specific_settings={LanguageServerId.SCALA: scala_settings})
|
||||
return ScalaLanguageServer._resolve_build_roots(str(root), settings)
|
||||
|
||||
def test_detection_is_used_when_unset(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "backend")
|
||||
assert self.resolve(tmp_path) == [str(tmp_path / "backend")]
|
||||
|
||||
def test_configured_roots_are_resolved_against_the_repository_root(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "a" / "b").mkdir(parents=True)
|
||||
assert self.resolve(tmp_path, project_roots=["a/b"]) == [str(tmp_path / "a" / "b")]
|
||||
|
||||
def test_configured_roots_need_not_look_like_builds(self, tmp_path: Path) -> None:
|
||||
"""The setting exists precisely for where the detection is wrong."""
|
||||
(tmp_path / "odd").mkdir()
|
||||
assert self.resolve(tmp_path, project_roots=["odd"]) == [str(tmp_path / "odd")]
|
||||
|
||||
def test_a_missing_configured_root_is_skipped(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "here").mkdir()
|
||||
assert self.resolve(tmp_path, project_roots=["here", "gone"]) == [str(tmp_path / "here")]
|
||||
|
||||
def test_detection_takes_over_when_no_configured_root_exists(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "backend")
|
||||
assert self.resolve(tmp_path, project_roots=["gone"]) == [str(tmp_path / "backend")]
|
||||
|
||||
@pytest.mark.parametrize("bad", ["backend", 42, [1, 2], []])
|
||||
def test_a_malformed_project_roots_falls_back_to_detection(self, tmp_path: Path, bad: object) -> None:
|
||||
make_sbt_build(tmp_path / "backend")
|
||||
assert self.resolve(tmp_path, project_roots=bad) == [str(tmp_path / "backend")]
|
||||
|
||||
@pytest.mark.parametrize("bad", [None, "3", 0, -1, True])
|
||||
def test_a_malformed_scan_depth_falls_back_to_the_default(self, tmp_path: Path, bad: object) -> None:
|
||||
make_sbt_build(tmp_path / "a" / "b" / "c")
|
||||
assert self.resolve(tmp_path, project_root_scan_depth=bad) == [str(tmp_path / "a" / "b" / "c")]
|
||||
assert DEFAULT_PROJECT_ROOT_SCAN_DEPTH == 3
|
||||
|
||||
def test_scan_depth_is_honoured(self, tmp_path: Path) -> None:
|
||||
make_sbt_build(tmp_path / "a" / "b" / "c")
|
||||
assert self.resolve(tmp_path, project_root_scan_depth=2) == [str(tmp_path)]
|
||||
|
||||
|
||||
@pytest.mark.scala
|
||||
class TestScalaInitializeParamsBuilder:
|
||||
@staticmethod
|
||||
def workspace_folder_paths(tmp_path: Path, build_roots: list[str], additional: list[str]) -> list[str]:
|
||||
ls = SimpleNamespace(
|
||||
repository_root_path=str(tmp_path),
|
||||
custom_settings={},
|
||||
config=LanguageServerConfig(ls_id=LanguageServerId.SCALA, additional_workspace_folders=additional),
|
||||
)
|
||||
params = ScalaInitializeParamsBuilder(cast(SolidLanguageServer, ls), build_roots).build()
|
||||
folders = params["workspaceFolders"] or []
|
||||
return [url2pathname(urlparse(folder["uri"]).path) for folder in folders]
|
||||
|
||||
def test_the_build_roots_become_the_workspace_folders(self, tmp_path: Path) -> None:
|
||||
roots = [str(tmp_path / "alpha"), str(tmp_path / "beta")]
|
||||
assert self.workspace_folder_paths(tmp_path, roots, additional=[]) == roots
|
||||
|
||||
def test_additional_workspace_folders_are_kept(self, tmp_path: Path) -> None:
|
||||
"""They can lie outside the repository, so detection could never recover them."""
|
||||
outside = tmp_path.parent / "shared-lib"
|
||||
outside.mkdir(exist_ok=True)
|
||||
roots = [str(tmp_path / "alpha")]
|
||||
assert self.workspace_folder_paths(tmp_path, roots, additional=[str(outside)]) == [*roots, str(outside)]
|
||||
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Unit tests for Serena's answer to Metals' `window/showMessageRequest` prompts.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.language_servers.scala_language_server import (
|
||||
_get_scala_settings,
|
||||
choose_show_message_request_action,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
# Metals' own prompts, with the message and action titles as its `Messages` object builds them
|
||||
# (scala/meta/internal/metals/Messages.scala) and `type` as lsp4j serialises `MessageType.Info`.
|
||||
IMPORT_BUILD = {
|
||||
"message": "New sbt workspace detected, would you like to import the build?",
|
||||
"type": 3,
|
||||
"actions": [{"title": "Import build"}, {"title": "Not now"}, {"title": "Don't show again"}],
|
||||
}
|
||||
IMPORT_CHANGES = {
|
||||
"message": "sbt build needs to be re-imported",
|
||||
"type": 3,
|
||||
"actions": [{"title": "Import changes"}, {"title": "Not now"}, {"title": "Don't show again"}],
|
||||
}
|
||||
GENERATE_BSP_AND_CONNECT = {
|
||||
"message": "New sbt workspace detected, would you like connect to the Bloop build server?",
|
||||
"type": 3,
|
||||
"actions": [{"title": "Connect"}, {"title": "Not now"}, {"title": "Don't show again"}],
|
||||
}
|
||||
OLD_BLOOP_VERSION_RUNNING = {
|
||||
"message": "Deprecated Bloop server is still running and is taking up resources, do you want to kill the process?",
|
||||
"type": 3,
|
||||
"actions": [{"title": "Yes"}, {"title": "Not now"}],
|
||||
}
|
||||
CHOOSE_BUILD_TOOL = {
|
||||
"message": "Multiple build definitions found. Which would you like to use?",
|
||||
"type": 3,
|
||||
"actions": [{"title": "sbt"}, {"title": "mill"}],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.scala
|
||||
class TestChooseShowMessageRequestAction:
|
||||
@pytest.mark.parametrize("params", [IMPORT_BUILD, IMPORT_CHANGES, GENERATE_BSP_AND_CONNECT])
|
||||
def test_build_import_prompts_are_answered_affirmatively(self, params: dict) -> None:
|
||||
chosen = choose_show_message_request_action(params)
|
||||
assert chosen is not None
|
||||
assert chosen["title"] in ("Import build", "Import changes", "Connect")
|
||||
|
||||
@pytest.mark.parametrize("params", [IMPORT_BUILD, IMPORT_CHANGES, GENERATE_BSP_AND_CONNECT])
|
||||
def test_auto_import_build_can_be_turned_off(self, params: dict) -> None:
|
||||
assert choose_show_message_request_action(params, auto_import_build=False) is None
|
||||
|
||||
def test_dont_show_again_is_never_chosen(self) -> None:
|
||||
"""Metals persists that dismissal in the project's own state."""
|
||||
params = {"message": "…", "actions": [{"title": "Don't show again"}, {"title": "Not now"}]}
|
||||
assert choose_show_message_request_action(params) is None
|
||||
|
||||
def test_an_unrecognised_prompt_is_dismissed(self) -> None:
|
||||
"""Answering "Yes" here would kill a process on the user's machine."""
|
||||
assert choose_show_message_request_action(OLD_BLOOP_VERSION_RUNNING) is None
|
||||
|
||||
def test_a_prompt_with_no_actions_is_dismissed(self) -> None:
|
||||
assert choose_show_message_request_action({"message": "just so you know"}) is None
|
||||
assert choose_show_message_request_action({"message": "just so you know", "actions": None}) is None
|
||||
|
||||
def test_malformed_actions_do_not_raise(self) -> None:
|
||||
params = {"message": "…", "actions": ["Import build", None, {"title": "Import build"}]}
|
||||
assert choose_show_message_request_action(params) == {"title": "Import build"}
|
||||
|
||||
def test_choosing_between_build_tools_is_left_alone(self) -> None:
|
||||
"""Naming the build tool for the user is a guess of a different order; see the comment
|
||||
on BUILD_IMPORT_PROMPT_ACTIONS. A workspace with several build definitions is therefore
|
||||
still not imported.
|
||||
"""
|
||||
assert choose_show_message_request_action(CHOOSE_BUILD_TOOL) is None
|
||||
|
||||
|
||||
@pytest.mark.scala
|
||||
class TestAutoImportBuildSetting:
|
||||
"""`auto_import_build` has to reach the handler from the project configuration."""
|
||||
|
||||
@staticmethod
|
||||
def setting(**scala_settings) -> object:
|
||||
settings = SolidLSPSettings(ls_specific_settings={LanguageServerId.SCALA: scala_settings})
|
||||
return _get_scala_settings(settings)["auto_import_build"]
|
||||
|
||||
def test_defaults_to_true(self) -> None:
|
||||
assert self.setting() is True
|
||||
assert _get_scala_settings(SolidLSPSettings())["auto_import_build"] is True
|
||||
|
||||
def test_can_be_disabled(self) -> None:
|
||||
assert self.setting(auto_import_build=False) is False
|
||||
@@ -428,7 +428,7 @@ name = "clr-loader"
|
||||
version = "0.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "python_full_version < '3.13' or sys_platform == 'win32'" },
|
||||
{ name = "cffi" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/46/7eea92b6aa2d68af78e049cbecec5f757f1aad44ecdecdc16bbad7eead51/clr_loader-0.3.1.tar.gz", hash = "sha256:2e073e9aaf49d1ae2f56ecba27987ad5fb68be4bcd9dd34a5bed8f0e4e128366", size = 86805, upload-time = "2026-04-18T17:49:44.287Z" }
|
||||
wheels = [
|
||||
@@ -455,61 +455,58 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "48.0.1"
|
||||
version = "50.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/12/45/870e7f4bef50e5f53b9f51d4428aee5290eedf58ba443f16b1ebb7ab8e66/cryptography-48.0.1.tar.gz", hash = "sha256:266f4ee051abb2f725b74ef8072b521ce1feacf685a3364fa6a6b45548db791a", size = 832989, upload-time = "2026-06-09T22:32:31.8Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1", size = 8008324, upload-time = "2026-06-09T22:31:00.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/85/6379d42181bfc713094f081360fc5784d6c816b599d45e7f082502d173ce/cryptography-48.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32143b24adb918f078134e1e230f1eb8cc04886b92c28b5f0041aaf3e5699225", size = 4696243, upload-time = "2026-06-09T22:32:33.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691", size = 4713235, upload-time = "2026-06-09T22:31:40.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/58/67cbf8cf1ee7c54b439ca07bbecf8362c07afc11a3724fea70f745784add/cryptography-48.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb86ce1af36fe65041b6db9a8bb064ee621a7e5fded0f80d475ec243477cd242", size = 4702323, upload-time = "2026-06-09T22:31:42.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c6/24266ac10c47f6cd2a865f4446062b466da1d1f10b27189eac00e61bf0c9/cryptography-48.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b024e784ad6c077ee0147b35ea9cbfc1e34e1fd4c1dcca214c2794d73a12df08", size = 5300085, upload-time = "2026-06-09T22:31:58.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/bb/cc4b78784f97efc8c5874c2a9743708d172be6663024b34a0467885ae0c8/cryptography-48.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3752f2dbc8f07a30aad2932c986cea495b03bb554887828225da104f732852b6", size = 4746137, upload-time = "2026-06-09T22:31:31.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/52/0c44de3f5267f8fbe8e835138017522a333436166e406f0db9b9e6e3033f/cryptography-48.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:bd81490cd5801d755cf97bb68ac191f14b708470b1c7cf4580f669b9c9264cd8", size = 4333867, upload-time = "2026-06-09T22:32:28.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/2e/772d7adbfa931537bc401640b7cac9976bff689bda187833e5d63b428e49/cryptography-48.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:66fd0771e7b9c6dcd44cf1120690d2338d16d72795cf40cae2786a39eba65429", size = 4701805, upload-time = "2026-06-09T22:31:38.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/a3/b06844f303873493c963caf581c04df31c7035e0c1b0f02c4814d319ec80/cryptography-48.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:3fd2ca57062b241c856670b073487d2e86c4637937ca5601e48f97bf8e11fc8f", size = 5258461, upload-time = "2026-06-09T22:31:04.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/13/8b765e2e12b07c74941caadb9d1c8fdc006c4dfbf2b8f2d610519758954d/cryptography-48.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:0ee6ea481db1ab889cba043ec1eda17bb9c1ea79db6722f779c3667f9f70322f", size = 4745488, upload-time = "2026-06-09T22:32:30.07Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/aa/48972bce55049b32a94f4907eda4d75fa385aad8a39506cc2fc72196ecf0/cryptography-48.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f2ceef93cb096aa3c4cc4b5c94ca6131f9196d28c64d6111533402a9b2054d41", size = 4830256, upload-time = "2026-06-09T22:31:43.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/a2/e5079a032fb85cf6005046ca92bbd78b0c82dad2b5751ab8c311659da06f/cryptography-48.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bd3f92d76217892b15df84ca256c2c113d386fdda7a7d8691aeeced976507c6", size = 4979117, upload-time = "2026-06-09T22:31:05.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/a0/8f50cae9c74e718ed769d63ed5c74bd0ea830c9550a74629cebd1b9c7bc7/cryptography-48.0.1-cp311-abi3-win32.whl", hash = "sha256:b9a32b876490d66c8bcc9963ef220199569748434ab01a9d6aaeabf88e7f5158", size = 3304154, upload-time = "2026-06-09T22:32:16.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24", size = 3817138, upload-time = "2026-06-09T22:32:00.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/06/3e768b4c3bc78201583fa35a0e18f640dd782ff41afba88f8545481a8874/cryptography-48.0.1-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:f817adc181390bd54f2f700107a7419040fb7c1bdf2fc26f36551a06a68c3345", size = 7989830, upload-time = "2026-06-09T22:31:07.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/13/6476736484b94041110c8340a3eb63962fea4975baea8cb4a512adb44d4d/cryptography-48.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d5d30989c6917b478b5817902e85fddaea2261efa8648383d965381ccb9e1ac4", size = 4689201, upload-time = "2026-06-09T22:31:09.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/62/65a87f34d2a431546e2509b85d55e8c90df86d668f6731da64d538512ac2/cryptography-48.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:df637c05205ea7c1d7fbcbe54bbfea648a52951155f997af13d895d0ecc96991", size = 4702822, upload-time = "2026-06-09T22:32:24.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/59/810b5204b0a9b10f4b6bc06bd551a8b609803cd931806bc3b71884b225e5/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:869c3b8a53bfe27147832df48b32adadf558249d50e76cb3769d40e986b13265", size = 4694875, upload-time = "2026-06-09T22:32:08.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/dc/d8ca05ffea724eec6d232ea6f18e74c269eb6bdfdcc9bfba689790d1325f/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:e361afba8918070d376df76f408a4f67fec0ee9cff81a99e48fe9a233ef59e17", size = 5290385, upload-time = "2026-06-09T22:31:15.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/8c/3be6cb4da181f5bb6c19cf560c2359d60644a6b5fc5b57854e528f47b296/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d069066deead00ac7f090be101be875a06855908f7ec004c27b8fefb4acfb411", size = 4737082, upload-time = "2026-06-09T22:32:22.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/f6/d5f60a5a1434dbfd949e227fd0065d194c7e6b6ac526b17f5c06152b8231/cryptography-48.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:09f73a725d582cef64b91281a322cd798d14a33b2b6f2b7ad9531dc336d84c02", size = 4325328, upload-time = "2026-06-09T22:32:10.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/b7/ba75dd947a14b6ad907b01ae8f6b5b348cdd1b48142f0063dee9e20c1d9d/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:15254441469dd6bf027039453288e2072124f8b6603563f5d759e1c9b69273fa", size = 4694530, upload-time = "2026-06-09T22:31:53.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/29/50d6b9e8aff12d8b67afaeb3569335e32dc83a5723e3bbded24fdac9f809/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:8ace4507d1e6533c125f4fac754f8bb8b6a74c08e92179dabd7e16571a3efbf3", size = 5245046, upload-time = "2026-06-09T22:31:25.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/04/618f4115cfc0add0838c82507aa18a346089428da8653ad38b3ff36f5cb3/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b4e391975f038e66432328639620a4aff2d307513b004f1ca06d6225bced815c", size = 4736660, upload-time = "2026-06-09T22:32:12.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/9c/06e062462a0de28a3b3911322eded4c16deb9f441b1b7575d3dc59488ab5/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42fcd8e26fe555d9b3577a135f5091fefa0aa4e99129c23fb56787a1bd4ada72", size = 4822229, upload-time = "2026-06-09T22:31:17.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/be/0561971eaaee4b8a0e7d5113c536921063ab91aaf23278ac374eaf881e11/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1400da5e32a43253392277eac7490a60e497d810a63dd5608d71bbd7af507c9", size = 4966364, upload-time = "2026-06-09T22:31:32.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/27/728c77876f12b000820b69ae490f3c4083775e79e07827e9e60be07ad209/cryptography-48.0.1-cp314-cp314t-win32.whl", hash = "sha256:0df56b056bc17c1b7d6821dfa65216e62bd232d8ab05eb3db44e71d235651471", size = 3278498, upload-time = "2026-06-09T22:31:29.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/e3/79a612c6d7b1e6ee0edd43633d53035bec2cfb78c82b76f7864f39e36f34/cryptography-48.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:9de21387aa95e2a895823d0745b430bed4f33503ba9ab5e0b5311f33e37d66d2", size = 3798790, upload-time = "2026-06-09T22:31:56.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/6c/00fa2a95997164c8b2072ce327c23d4ab20809ccc323ea5fab91e53a4bba/cryptography-48.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:4fdc69f8e4316bcf0c8c8ec1f26f285d12e8142d88d96c876a59a03be3f6ae67", size = 7987408, upload-time = "2026-06-09T22:32:20.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/d9/45f309a7e4e5f3f8f121d6d3be9e94024a7726ec598d6e08ae04edb2f04d/cryptography-48.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48fe40804d4caa2288f24e70ca8c64c42dd826da0ad7e4f1b41b2128d679e6c8", size = 4690196, upload-time = "2026-06-09T22:31:54.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/9f/a1bc8bcc798811b8527eb374bbccf30a3f3e806829d967118222bf1125eb/cryptography-48.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86be3b1b0b6bf09482fb50a979c508d2950ed95f5621ec77f4e385962006b83a", size = 4696782, upload-time = "2026-06-09T22:31:45.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/c2/81a4fb4e4373c500bb526bc337ac5719dd31dd15b970b84a238168c6aa08/cryptography-48.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab0a343c807bbcd90c971cd1ecf072937cd01847a9e002bef88fb47ac6be577", size = 4696618, upload-time = "2026-06-09T22:31:11.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/0b/aa68b221dde92d09cb29a024ede17550ee21e77a404e59fc093c82bb51e1/cryptography-48.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9621de99d2da096006b629979efd8ae7eb2d8b822488d0c89ee4000c306c59b1", size = 5289970, upload-time = "2026-06-09T22:31:20.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/13/fba657f958d2af66ea959a4ba01212632089249d34af1ae48054136344d7/cryptography-48.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:88c852a0ae366e262e5a1744b685e6a433dc8788dd2a277e418bf4904203609d", size = 4731873, upload-time = "2026-06-09T22:31:22.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/4c/9a964756d24a26b3e34dfcb16f961b89838786e6700b635b0d1e3adff4b6/cryptography-48.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:43c5835e2cb98c8733d86f57d6fc879b613f5c3478607281c3e36daffc6dd8a6", size = 4330804, upload-time = "2026-06-09T22:31:36.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/0f/a10f3a6eb12950a10e3a874070283aa2dd5875b2bfd15fad8a3e17b3f13e/cryptography-48.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46", size = 4696217, upload-time = "2026-06-09T22:31:13.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/6f/5cd12f951165ea73ef85266775d97e4c763b2474ccfd816dd69d3a18d6f8/cryptography-48.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:b7a2d1a937a738a881737cec135a38bb61470589b17515b9f73f571d0ae10401", size = 5245252, upload-time = "2026-06-09T22:32:02.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/ab/8aaa12e4516ec4464033ab79b6f3b592bd5a92102467c4ace8a0d970203f/cryptography-48.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b74ca3b8e5ecdd833bf6a002ca41b4793bb27fb8f1c06ffaf2643c9e9140e31b", size = 4731388, upload-time = "2026-06-09T22:32:04.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/24/50027ea4dca85ec1f40688f3c24fb32ccacd520583c9592c3cc95628e6fb/cryptography-48.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c37f2461406063b417837f5f3daab668652acd82423efcd7f0a9f04be972de1", size = 4824186, upload-time = "2026-06-09T22:32:18.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/41/04cb5eb17085ade6f50cc611fb657df6a0f5885350de8764ece89c050197/cryptography-48.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86fe77abb1bd87afb251d4d02ada7ecf53a32cee9b67d976abb2e45a13297475", size = 4964539, upload-time = "2026-06-09T22:31:18.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/bf/ed70785c496e89d7e73b7cda2d21f2447fd6d4e821714b8d04ff217fed92/cryptography-48.0.1-cp39-abi3-win32.whl", hash = "sha256:6b2c0c3e6ccf3ade7750f836ef3ee36eea250cc467d45c256895573ac08cc6f1", size = 3282307, upload-time = "2026-06-09T22:30:53.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/ff/371ea7d252656ee1eb6d83eeeef3d1d0c6baf1d6497687d081ea03814670/cryptography-48.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:9a49ca6c81417f6a5edb50375a60cccdd70fa0a91a5211829dbea74eba94d2ac", size = 3793408, upload-time = "2026-06-09T22:32:15.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/d3/eb4e394e587341fdad09a09101fa76478ead3a78b0ad63e55c22f0d75c02/cryptography-48.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:08a597acce1ff37f347400087776599e2348a3a8bc53b44120e463cd274efe4a", size = 3951747, upload-time = "2026-06-09T22:31:23.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/4a/3f43451b4f858bfceaaaffc649e6e787e8d4fb332a1d443af39ab02cc8f1/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:735824ec41b7f74a7c45fb1591349333e4c696cb6c044e5f46356e560143e4cd", size = 4641226, upload-time = "2026-06-09T22:31:02.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/4e/855584c2c23b09e4ce2d3b9c30e983e679cd60b068c513c6bbdb91e11782/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:92a46e1d638daa264ba2971c0b0489c9409787943efae4d60ffda3d091ef832c", size = 4668958, upload-time = "2026-06-09T22:32:06.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/3b/d35750e41d803d1e516fd6d6011f065424924da7af1748cef4cc9cb3ede1/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:7e234ac052af99f2700826a5c29ea99d9c1b1f80341cde62d11c8154dc8e0bd9", size = 4640793, upload-time = "2026-06-09T22:32:26.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/aa/cdb7181fe865285e87e96825aaab239400f1de0c3bfba9bd9769b79f1a92/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:33842cf0888951cef5bc7ac724ab844a42044c1727b967b7f8997289a0464f92", size = 4668505, upload-time = "2026-06-09T22:31:27.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/8c/ce3823c06c2804f194f9e64f0d67fa3f4094a39f2bb1a990cd03603af8fc/cryptography-48.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6184ca7b174f28d7c703f1290d4b297217c45355f77a98f67e9b7f14549ac54a", size = 3742204, upload-time = "2026-06-09T22:31:34.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/3e/e54cde8c01631a5a8226ccd617eab9e57fd5cfdad90f1a9e6bb570794631/cryptography-50.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c", size = 3963170, upload-time = "2026-07-31T14:24:51.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/b6/0b9e125e90f3d2dcf599a218a899cda7326a3158cfa258723f0b398b08f6/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a", size = 4692441, upload-time = "2026-07-31T14:24:53.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/c9/a5151588710785a96d7bc4de27d4cd62f263bbbcb203cfe29df537eb6505/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e", size = 4699810, upload-time = "2026-07-31T14:24:55.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/1a/15b92b25eb6ce3089cd49377ae990a0f3ad485a510f968aed1f19dbdcdf2/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d", size = 4691924, upload-time = "2026-07-31T14:24:58.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/15/219075012ab13e8905f3cd572204f4acb4b111df787104346b9bc0cea789/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437", size = 4699593, upload-time = "2026-07-31T14:24:59.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/b5/c2c5fce26f0ee40d21bafe7f191d29a34b35a65ac4fe8a1191d1983612e9/cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9", size = 3813796, upload-time = "2026-07-31T14:25:02.298Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -689,14 +686,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "gitpython"
|
||||
version = "3.1.46"
|
||||
version = "3.1.58"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "gitdb" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/26/d6/5f358ff283325580c2003a6d953aea18cfe10ae87b46f5ebc80fa3a386dc/gitpython-3.1.58.tar.gz", hash = "sha256:621416df10ef3fd0e19fabf9172ddeed0fa704d353d04f194eec56a625a95b22", size = 228498, upload-time = "2026-08-04T15:05:49.47Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/0c/9d8752098bc442f0726e64aa6135940b3a96809915d1aa4206c1bb97881d/gitpython-3.1.58-py3-none-any.whl", hash = "sha256:d331e722577f0fd7fc1f857419b3ecc07af66282b933d2a4d95f84a042fdd50f", size = 220183, upload-time = "2026-08-04T15:05:48.025Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -789,24 +786,24 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "4.3.0"
|
||||
version = "4.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "hpack" },
|
||||
{ name = "hyperframe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/85/7c366e69d84c17bb778fe41419e1fbcce3033d5b7ce29bbffff0a98b859f/h2-4.4.1.tar.gz", hash = "sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516", size = 2157281, upload-time = "2026-08-03T11:45:09.509Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/22/e85faf23bd72a92d1921e37d674ca56eb298a3c8be31fdecef0ff2b3aaac/h2-4.4.1-py3-none-any.whl", hash = "sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6", size = 62636, upload-time = "2026-08-03T11:44:59.164Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hpack"
|
||||
version = "4.1.0"
|
||||
version = "4.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/26/5b/fcabf6028144a8723726318b07a32c2f3314acdff6265743cf08a344b18e/hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0", size = 51300, upload-time = "2026-06-23T18:34:46.667Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/b4/4a9fcfb2aef6ba44d9073ecd301443aa00b3dac95de5619f2a7de7ec8a91/hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986", size = 34246, upload-time = "2026-06-23T18:34:45.472Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -945,17 +942,17 @@ resolution-markers = [
|
||||
"python_full_version < '3.12'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" },
|
||||
{ name = "decorator", marker = "python_full_version < '3.12'" },
|
||||
{ name = "ipython-pygments-lexers", marker = "python_full_version < '3.12'" },
|
||||
{ name = "jedi", marker = "python_full_version < '3.12'" },
|
||||
{ name = "matplotlib-inline", marker = "python_full_version < '3.12'" },
|
||||
{ name = "pexpect", marker = "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "prompt-toolkit", marker = "python_full_version < '3.12'" },
|
||||
{ name = "pygments", marker = "python_full_version < '3.12'" },
|
||||
{ name = "stack-data", marker = "python_full_version < '3.12'" },
|
||||
{ name = "traitlets", marker = "python_full_version < '3.12'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.12'" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "decorator" },
|
||||
{ name = "ipython-pygments-lexers" },
|
||||
{ name = "jedi" },
|
||||
{ name = "matplotlib-inline" },
|
||||
{ name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "prompt-toolkit" },
|
||||
{ name = "pygments" },
|
||||
{ name = "stack-data" },
|
||||
{ name = "traitlets" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c5/25/daae0e764047b0a2480c7bbb25d48f4f509b5818636562eeac145d06dfee/ipython-9.10.1.tar.gz", hash = "sha256:e170e9b2a44312484415bdb750492699bf329233b03f2557a9692cce6466ada4", size = 4426663, upload-time = "2026-03-27T09:53:26.244Z" }
|
||||
wheels = [
|
||||
@@ -974,16 +971,16 @@ resolution-markers = [
|
||||
"python_full_version == '3.12.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" },
|
||||
{ name = "decorator", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "jedi", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "matplotlib-inline", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "prompt-toolkit", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "pygments", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "stack-data", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "traitlets", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "decorator" },
|
||||
{ name = "ipython-pygments-lexers" },
|
||||
{ name = "jedi" },
|
||||
{ name = "matplotlib-inline" },
|
||||
{ name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "prompt-toolkit" },
|
||||
{ name = "pygments" },
|
||||
{ name = "stack-data" },
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/73/7114f80a8f9cabdb13c27732dce24af945b2923dcab80723602f7c8bc2d8/ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4", size = 4428879, upload-time = "2026-03-27T09:42:45.312Z" }
|
||||
wheels = [
|
||||
@@ -1641,7 +1638,7 @@ name = "pexpect"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ptyprocess", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "ptyprocess" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
|
||||
wheels = [
|
||||
@@ -1650,89 +1647,66 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.2.0"
|
||||
version = "12.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1819,11 +1793,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2122,7 +2096,7 @@ name = "pyobjc-framework-cocoa"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-core" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" }
|
||||
wheels = [
|
||||
@@ -2139,8 +2113,8 @@ name = "pyobjc-framework-quartz"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" }
|
||||
wheels = [
|
||||
@@ -2157,8 +2131,8 @@ name = "pyobjc-framework-security"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" }
|
||||
wheels = [
|
||||
@@ -2175,8 +2149,8 @@ name = "pyobjc-framework-uniformtypeidentifiers"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" }
|
||||
wheels = [
|
||||
@@ -2188,8 +2162,8 @@ name = "pyobjc-framework-webkit"
|
||||
version = "12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" }
|
||||
wheels = [
|
||||
@@ -2291,7 +2265,7 @@ name = "python-xlib"
|
||||
version = "0.33"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" }
|
||||
wheels = [
|
||||
@@ -2303,7 +2277,7 @@ name = "pythonnet"
|
||||
version = "3.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "clr-loader", marker = "python_full_version < '3.13' or sys_platform == 'win32'" },
|
||||
{ name = "clr-loader" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/57/da1992e44663b71365c6e842c8d7fa453d4ec45fb99a68cfee5b7e944d3c/pythonnet-3.1.0.tar.gz", hash = "sha256:7b34c382905d10a371509ffafd64cae0416305c28817738a9cd138336f4e9991", size = 250599, upload-time = "2026-05-23T20:30:21.578Z" }
|
||||
wheels = [
|
||||
@@ -2450,7 +2424,7 @@ name = "qtpy"
|
||||
version = "2.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging", marker = "python_full_version < '3.13' or sys_platform != 'win32'" },
|
||||
{ name = "packaging" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" }
|
||||
wheels = [
|
||||
@@ -2885,7 +2859,7 @@ requires-dist = [
|
||||
{ name = "agno", marker = "extra == 'agno'", specifier = "==2.6.6" },
|
||||
{ name = "anthropic", specifier = "==0.117.0" },
|
||||
{ name = "beautifulsoup4", specifier = "==4.14.2" },
|
||||
{ name = "cryptography", specifier = "==48.0.1" },
|
||||
{ name = "cryptography", specifier = "==50.0.0" },
|
||||
{ name = "docstring-parser", specifier = "==0.17.0" },
|
||||
{ name = "dotenv", specifier = "==0.9.9" },
|
||||
{ name = "filelock", specifier = "==3.25.2" },
|
||||
@@ -2902,7 +2876,7 @@ requires-dist = [
|
||||
{ name = "pathspec", specifier = "==0.12.1" },
|
||||
{ name = "poethepoet", marker = "extra == 'dev'", specifier = "==0.36.0" },
|
||||
{ name = "psutil", specifier = "==7.0.0" },
|
||||
{ name = "pyasn1", marker = "extra == 'dev'", specifier = "==0.6.3" },
|
||||
{ name = "pyasn1", marker = "extra == 'dev'", specifier = "==0.6.4" },
|
||||
{ name = "pydantic", specifier = "==2.12.5" },
|
||||
{ name = "pygls", specifier = "==2.1.1" },
|
||||
{ name = "pyinstrument", marker = "extra == 'dev'", specifier = "==5.1.1" },
|
||||
@@ -2944,11 +2918,11 @@ provides-extras = ["dev", "agno", "google"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "82.0.1"
|
||||
version = "83.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user