mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-05 06:14:06 +00:00
Security enhancement: pinned all runtime deps, verify checksums
Extended documentation of ls_specific_settings and of our security approach
This commit is contained in:
1 parent
c6250aeeab
commit
f00a992e5d
40 files changed
+1724
-614
No files matched your search
@@ -226,16 +226,10 @@ ls_specific_settings:
|
||||
# language-server-specific keys
|
||||
```
|
||||
|
||||
:::{attention}
|
||||
Most settings are currently undocumented. Please refer to the
|
||||
[source code of the respective language server](https://github.com/oraios/serena/tree/main/src/solidlsp/language_servers)
|
||||
implementation to determine supported settings.
|
||||
:::
|
||||
|
||||
(override-ls-path)=
|
||||
#### Overriding the Language Server Path
|
||||
|
||||
Some language servers, particularly those that use a single core path for the language server (e.g. the main executable),
|
||||
Most of Serena's language servers, particularly those that use a single core path for the language server (e.g. the main executable),
|
||||
support overriding that path via the `ls_path` setting.
|
||||
Therefore, if you have installed the language server yourself and want to use your installation
|
||||
instead of Serena's managed installation, you can set the `ls_path` setting as follows:
|
||||
@@ -246,9 +240,87 @@ ls_specific_settings:
|
||||
ls_path: "/path/to/language-server"
|
||||
```
|
||||
|
||||
This is supported by all language servers deriving their dependency provider from `LanguageServerDependencyProviderSinglePath`.
|
||||
Currently, this includes the following languages: `bash`, `clojure`, `cpp`, `kotlin`, `markdown`, `php`, `php_phpactor`, `python`, `rust`, `toml`, `typescript`, `yaml`.
|
||||
We will add support for more languages over time.
|
||||
This is supported by all language servers deriving their dependency provider from `LanguageServerDependencyProviderSinglePath`,
|
||||
and by some additional wrappers that explicitly expose `ls_path`.
|
||||
Common examples include: `ansible`, `bash`, `clojure`, `cpp`, `cpp_ccls`, `hlsl`, `kotlin`, `lean4`, `luau`, `markdown`, `php`,
|
||||
`php_phpactor`, `python`, `rust`, `solidity`, `systemverilog`, `toml`, `typescript`, and `yaml`.
|
||||
|
||||
If a language server supports `ls_path`, setting it bypasses Serena's managed download or install for that server.
|
||||
In that case, any server-specific version or registry settings only apply when `ls_path` is not set.
|
||||
|
||||
#### AL
|
||||
|
||||
Serena uses the AL language server bundled in the Microsoft Dynamics 365 Business Central VS Code extension.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `al_extension_version` | `18.0.2242655` | Override the AL VS Code extension version Serena downloads from the VS Code Marketplace. |
|
||||
|
||||
#### Ansible
|
||||
|
||||
Serena uses `@ansible/ansible-language-server` for the `ansible` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed install | Override the `ansible-language-server` executable path. |
|
||||
| `ansible_language_server_version` | `1.2.3` | 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. |
|
||||
| `ansible_path` | `"ansible"` | Path to the `ansible` executable forwarded to the language server. |
|
||||
| `ansible_settings` | `null` | Full Ansible LS settings dict, deep-merged on top of Serena's defaults. |
|
||||
| `lint_enabled` | `false` | Enable `ansible-lint` integration. |
|
||||
| `lint_path` | `"ansible-lint"` | Path to the `ansible-lint` executable. |
|
||||
| `python_interpreter_path` | `"python3"` | Python interpreter path forwarded to the language server. |
|
||||
| `python_activation_script` | `""` | Virtualenv activation script forwarded to the language server. |
|
||||
|
||||
#### Bash
|
||||
|
||||
Serena uses `bash-language-server` for Bash support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed install | Override the `bash-language-server` executable path. |
|
||||
| `bash_language_server_version` | `5.6.0` | 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. |
|
||||
|
||||
#### Clojure
|
||||
|
||||
Serena uses `clojure-lsp` for Clojure support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed download | Override the `clojure-lsp` executable path. |
|
||||
| `clojure_lsp_version` | `2026.02.20-16.08.58` | Override the `clojure-lsp` release version Serena downloads when `ls_path` is not set. |
|
||||
|
||||
#### C/C++ (`clangd`)
|
||||
|
||||
Serena uses `clangd` for the `cpp` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed download | Override the `clangd` executable path. |
|
||||
| `compile_commands_dir` | `.serena` | Directory where Serena writes a transformed `compile_commands.json` if the project's original database uses relative `directory` entries. |
|
||||
| `clangd_version` | `19.1.2` | Override the `clangd` version Serena downloads when `ls_path` is not set. |
|
||||
|
||||
#### C/C++ via `ccls`
|
||||
|
||||
Serena uses the `cpp_ccls` language key for `ccls`.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | `ccls` from PATH | Override the `ccls` executable path. Serena does not manage `ccls` downloads or installs. |
|
||||
|
||||
|
||||
#### C# (Roslyn Language Server)
|
||||
|
||||
@@ -266,12 +338,14 @@ Automatic download is supported for: Windows (x64, ARM64), macOS (x64, ARM64), L
|
||||
**Configuration:**
|
||||
|
||||
The `runtime_dependencies` setting allows you to override the download URLs for the Roslyn Language Server. This is useful if you need to use a private package mirror or a specific version.
|
||||
For the common case of changing only the package version, use `csharp_language_server_version`.
|
||||
|
||||
Example configuration to override the language server download URL:
|
||||
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
csharp:
|
||||
csharp_language_server_version: "5.5.0-2.26078.4"
|
||||
runtime_dependencies:
|
||||
- id: "CSharpLanguageServer"
|
||||
platform_id: "linux-x64" # or win-x64, win-arm64, osx-x64, osx-arm64, linux-arm64
|
||||
@@ -294,6 +368,59 @@ Notes:
|
||||
- The language server package is a `.nupkg` file (ZIP format) downloaded from NuGet.org by default.
|
||||
- If you have .NET 10+ already installed, Serena will use your system installation.
|
||||
|
||||
#### C# (`OmniSharp`)
|
||||
|
||||
Serena uses the `csharp_omnisharp` language key for OmniSharp.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `omnisharp_version` | `1.39.10` | Override the OmniSharp version Serena downloads. |
|
||||
| `razor_omnisharp_version` | `7.0.0-preview.23363.1` | Override the Razor OmniSharp plugin version Serena downloads. |
|
||||
|
||||
#### Dart
|
||||
|
||||
Serena uses the Dart SDK's built-in language server for Dart support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `dart_sdk_version` | `3.7.1` | Override the Dart SDK version Serena downloads. |
|
||||
|
||||
#### Elixir
|
||||
|
||||
Serena uses [Expert](https://github.com/elixir-lang/expert) for Elixir support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `expert_version` | `v0.1.0-rc.6` | Override the Expert version Serena downloads when it does not use an `expert` executable already found in PATH. |
|
||||
|
||||
#### Elm
|
||||
|
||||
Serena uses `@elm-tooling/elm-language-server` for Elm support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `elm_language_server_version` | `2.8.0` | Override the npm package version Serena installs when no system `elm-language-server` is found. |
|
||||
| `npm_registry` | `null` | Override the npm registry Serena uses for the managed install. |
|
||||
|
||||
#### F#
|
||||
|
||||
Serena uses FsAutoComplete (Ionide LSP) for F# support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `fsautocomplete_version` | `0.83.0` | Override the FsAutoComplete version Serena installs as a .NET tool. |
|
||||
|
||||
|
||||
#### Go (`gopls`)
|
||||
|
||||
Serena forwards `ls_specific_settings.go.gopls_settings` to `gopls` as LSP `initializationOptions` when the Go language server is started.
|
||||
@@ -318,6 +445,45 @@ Notes:
|
||||
- `GOFLAGS` (from the environment you start Serena in) may also affect the Go build context. Prefer `buildFlags` for tags.
|
||||
- Build context changes are only picked up when `gopls` starts. After changing `gopls_settings` (or relevant env vars like `GOFLAGS`), restart the Serena process (or server) that hosts the Go language server, or use your client's "Restart language server" action if it causes `gopls` to restart.
|
||||
|
||||
#### Groovy
|
||||
|
||||
Serena uses a user-provided Groovy Language Server JAR for Groovy support. If `ls_java_home_path` is not set, Serena downloads
|
||||
a bundled Java runtime for launching that JAR.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_jar_path` | required | Path to the Groovy Language Server JAR |
|
||||
| `ls_java_home_path` | `null` | Path to a Java installation to use instead of Serena's managed runtime |
|
||||
| `ls_jar_options` | `""` | Additional options passed when launching the Groovy LS JAR |
|
||||
| `vscode_java_version` | `1.42.0-561` | Override the bundled Java runtime bundle version Serena downloads by default |
|
||||
|
||||
Note:
|
||||
- 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.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
groovy:
|
||||
ls_jar_path: "/path/to/groovy-language-server-all.jar"
|
||||
vscode_java_version: "1.42.0-561"
|
||||
```
|
||||
|
||||
#### HLSL
|
||||
|
||||
Serena uses `shader-language-server` for the `hlsl` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed install or build | Override the `shader-language-server` executable path. |
|
||||
| `version` | `1.3.0` | Override the bundled version Serena downloads, or builds from source on macOS, when `ls_path` is not set. |
|
||||
|
||||
|
||||
#### Java (`eclipse.jdt.ls`)
|
||||
|
||||
The following settings are supported for the Java language server:
|
||||
@@ -329,6 +495,13 @@ The following settings are supported for the Java language server:
|
||||
| `gradle_wrapper_enabled` | `false` | Use the project's Gradle wrapper (`gradlew`) instead of the bundled Gradle distribution. Enable this for projects with custom plugins or repositories. |
|
||||
| `gradle_java_home` | `null` | Path to the JDK used by Gradle. When unset, Gradle uses the bundled JRE. |
|
||||
| `use_system_java_home` | `false` | Use the system's `JAVA_HOME` environment variable for JDTLS itself. Enable this if your project requires a specific JDK vendor or version for Gradle's JDK checks. |
|
||||
| `gradle_version` | `8.14.2` | Override the Gradle distribution version Serena downloads by default. |
|
||||
| `vscode_java_version` | `1.42.0-561` | Override the bundled `vscode-java` runtime bundle version Serena downloads by default. |
|
||||
| `intellicode_version` | `1.2.30` | Override the IntelliCode VSIX version Serena downloads by default. |
|
||||
|
||||
Note:
|
||||
- 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.
|
||||
|
||||
Example for a project with custom Gradle plugins and JDK requirements:
|
||||
|
||||
@@ -343,21 +516,44 @@ ls_specific_settings:
|
||||
|
||||
Serena uses [JetBrains' Kotlin Language Server](https://github.com/Kotlin/kotlin-lsp) for Kotlin support.
|
||||
|
||||
**Runtime Requirements:**
|
||||
Supported settings:
|
||||
|
||||
- Java 21 or higher is required. If not found, Serena automatically downloads an appropriate JRE.
|
||||
- The Kotlin Language Server is automatically downloaded from JetBrains' CDN.
|
||||
| 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. |
|
||||
| `jvm_options` | `-Xmx2G` | Value assigned to `JAVA_TOOL_OPTIONS` for the Kotlin LS process. Set to `""` to disable JVM options entirely. |
|
||||
|
||||
**Configuration:**
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
kotlin:
|
||||
ls_path: "/path/to/kotlin-lsp.sh" # Override the Kotlin Language Server executable
|
||||
kotlin_lsp_version: "261.13587.0" # Override the Kotlin Language Server version
|
||||
jvm_options: "-Xmx4G -XX:+UseG1GC" # JVM options (default: -Xmx2G). Set to "" to disable.
|
||||
kotlin_lsp_version: "261.13587.0"
|
||||
jvm_options: "-Xmx4G -XX:+UseG1GC"
|
||||
```
|
||||
|
||||
#### Lean 4
|
||||
|
||||
Serena uses `lean --server` for Lean 4 support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | `lean` from PATH | Override the `lean` executable path. Serena does not manage Lean downloads. |
|
||||
|
||||
#### Lua
|
||||
|
||||
Serena uses `lua-language-server` for Lua support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `lua_language_server_version` | `3.15.0` | Override the bundled `lua-language-server` version Serena downloads when it cannot use an existing installation from PATH or common install locations. |
|
||||
|
||||
|
||||
#### Luau
|
||||
|
||||
Serena uses [`luau-lsp`](https://github.com/JohnnyMorganz/luau-lsp) for Luau support.
|
||||
@@ -373,6 +569,7 @@ Serena uses [`luau-lsp`](https://github.com/JohnnyMorganz/luau-lsp) for Luau sup
|
||||
ls_specific_settings:
|
||||
luau:
|
||||
ls_path: "/path/to/luau-lsp" # Optional: override the language server executable
|
||||
luau_lsp_version: "1.63.0" # Optional: override the bundled luau-lsp version
|
||||
platform: "roblox" # "roblox" (default) or "standard"
|
||||
roblox_security_level: "PluginSecurity" # Roblox only: None, PluginSecurity, LocalUserSecurity, RobloxScriptSecurity
|
||||
```
|
||||
@@ -381,6 +578,29 @@ Notes:
|
||||
- In `roblox` mode, Serena downloads Roblox definitions and Roblox API docs and passes them to `luau-lsp`.
|
||||
- In `standard` mode, Serena skips Roblox definitions and only downloads the standard Luau docs bundle.
|
||||
|
||||
#### Markdown
|
||||
|
||||
Serena uses [Marksman](https://github.com/artempyanykh/marksman) for the `markdown` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed download | Override the `marksman` executable path. |
|
||||
| `marksman_version` | `2024-12-18` | Override the Marksman release tag Serena downloads when `ls_path` is not set. |
|
||||
|
||||
#### MATLAB
|
||||
|
||||
Serena uses the official MathWorks MATLAB language server from the VS Code extension.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `matlab_path` | auto-detected | Path to the MATLAB installation. This overrides `MATLAB_PATH` and auto-detection, but not Serena's managed extension download. |
|
||||
| `matlab_extension_version` | `1.3.9` | Override the MathWorks VS Code extension version Serena downloads. |
|
||||
|
||||
|
||||
#### Pascal (`pasls`)
|
||||
|
||||
Serena uses [pasls](https://github.com/genericptr/pascal-language-server) (Pascal Language Server) for Pascal/Free Pascal support.
|
||||
@@ -405,6 +625,7 @@ Configure pasls via `ls_specific_settings.pascal` in `serena_config.yml`:
|
||||
|
||||
| Setting | Description |
|
||||
| ---------------- | --------------------------------------------------------------------------- |
|
||||
| `pasls_version` | Override the pinned pasls version Serena downloads by default |
|
||||
| `pp` | Path to FPC compiler driver (must be `fpc` or `fpc.exe`, not `ppc386.exe`) |
|
||||
| `fpcdir` | Path to FPC source directory |
|
||||
| `lazarusdir` | Path to Lazarus directory (required for LCL projects) |
|
||||
@@ -426,6 +647,183 @@ Notes:
|
||||
- Use the FPC compiler driver (`fpc`/`fpc.exe`), not backend compilers like `ppc386.exe`.
|
||||
- These settings are passed as environment variables to the pasls process.
|
||||
|
||||
#### PHP (`Intelephense`)
|
||||
|
||||
Serena uses Intelephense for the `php` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed install | Override the `intelephense` executable path. |
|
||||
| `intelephense_version` | `1.14.4` | 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. |
|
||||
| `ignore_vendor` | `true` | Ignore directories named `vendor` while indexing the project. |
|
||||
| `maxFileSize` | unset | Forwarded as `intelephense.files.maxSize` in `initializationOptions`. |
|
||||
| `maxMemory` | unset | Forwarded as `intelephense.maxMemory` in `initializationOptions`. |
|
||||
|
||||
#### PHP (`Phpactor`)
|
||||
|
||||
Serena uses the `php_phpactor` language key for Phpactor.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed download | Override the Phpactor PHAR path. |
|
||||
| `phpactor_version` | `2025.12.21.1` | Override the Phpactor PHAR version Serena downloads when `ls_path` is not set. |
|
||||
| `ignore_vendor` | `true` | Ignore directories named `vendor` while indexing the project. |
|
||||
|
||||
#### PowerShell
|
||||
|
||||
Serena uses PowerShell Editor Services for PowerShell support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `pses_version` | `4.4.0` | Override the PowerShell Editor Services version Serena downloads. Serena still requires `pwsh` to be available locally. |
|
||||
|
||||
#### Python
|
||||
|
||||
Serena uses Pyright for the `python` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | current Python executable | Override the Python interpreter Serena uses to run `-m pyright.langserver`. |
|
||||
|
||||
Note:
|
||||
- There is currently no separate `python_ty` language key in Serena's current SolidLSP implementation.
|
||||
|
||||
#### Ruby
|
||||
|
||||
Serena uses Shopify's `ruby-lsp` for Ruby support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ruby_lsp_version` | `0.26.8` | Override the `ruby-lsp` gem version Serena installs when no project-local or global `ruby-lsp` is already available. |
|
||||
|
||||
#### Rust
|
||||
|
||||
Serena uses `rust-analyzer` for Rust support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | auto-detected | Override the `rust-analyzer` executable path. Without `ls_path`, Serena prefers `rustup which rust-analyzer`, then `rustup component add rust-analyzer`, then PATH/common install locations. |
|
||||
|
||||
#### Scala
|
||||
|
||||
Serena uses Metals for Scala support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `metals_version` | `1.6.4` | Override the Metals version Serena bootstraps. |
|
||||
| `client_name` | `Serena` | Client identifier sent to Metals. |
|
||||
| `on_stale_lock` | `auto-clean` | How Serena handles stale Metals H2 database locks. Supported values: `auto-clean`, `warn`, `fail`. |
|
||||
| `log_multi_instance_notice` | `true` | Log a notice when another Metals instance is detected. |
|
||||
|
||||
#### Solidity
|
||||
|
||||
Serena uses `@nomicfoundation/solidity-language-server` for Solidity support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed install | Override the Solidity language server executable path. |
|
||||
| `solidity_language_server_version` | `0.8.4` | 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. |
|
||||
|
||||
#### SystemVerilog
|
||||
|
||||
Serena uses `verible-verilog-ls` for SystemVerilog support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | system PATH or managed download | Override the `verible-verilog-ls` executable path. |
|
||||
| `verible_version` | `v0.0-4051-g9fdb4057` | Override the Verible release Serena downloads when `ls_path` is not set and no system installation is found. |
|
||||
|
||||
#### Terraform
|
||||
|
||||
Serena uses `terraform-ls` for Terraform support.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `terraform_ls_version` | `0.36.5` | Override the `terraform-ls` version Serena downloads. Terraform itself must still be installed and available in PATH. |
|
||||
|
||||
#### TOML
|
||||
|
||||
Serena uses [Taplo](https://github.com/tamasfe/taplo) for the `toml` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed download | Override the `taplo` executable path. |
|
||||
| `taplo_version` | `0.10.0` | Override the Taplo version Serena downloads when `ls_path` is not set. |
|
||||
|
||||
#### TypeScript
|
||||
|
||||
Serena uses `typescript-language-server` for the `typescript` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed install | Override the `typescript-language-server` executable path. |
|
||||
| `typescript_version` | `5.9.3` | Override the bundled `typescript` npm package version Serena installs when `ls_path` is not set. |
|
||||
| `typescript_language_server_version` | `5.1.3` | Override the bundled `typescript-language-server` npm package version Serena installs when `ls_path` is not set. |
|
||||
| `npm_registry` | `null` | Override the npm registry Serena uses for the managed install. |
|
||||
|
||||
#### TypeScript via `vtsls`
|
||||
|
||||
The actual configuration key for vtsls is `typescript_vts`, not `vts`.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `vtsls_version` | `0.2.9` | Override the `@vtsls/language-server` npm package version Serena installs. |
|
||||
| `npm_registry` | `null` | Override the npm registry Serena uses for the managed install. |
|
||||
|
||||
#### Vue
|
||||
|
||||
Serena uses `@vue/language-server` (Volar) for the `vue` language key, together with a companion TypeScript language server.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `vue_language_server_version` | `3.1.5` | Override the bundled `@vue/language-server` npm package version Serena installs. |
|
||||
| `npm_registry` | `null` | Override the npm registry Serena uses for the managed install. If unset on `vue`, Serena falls back to `ls_specific_settings.typescript.npm_registry`. |
|
||||
|
||||
Notes:
|
||||
- `typescript_version` and `typescript_language_server_version` are read from `ls_specific_settings.typescript`, not from `ls_specific_settings.vue`.
|
||||
|
||||
#### YAML
|
||||
|
||||
Serena uses `yaml-language-server` for the `yaml` language key.
|
||||
|
||||
Supported settings:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed install | Override the `yaml-language-server` executable path. |
|
||||
| `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
|
||||
|
||||
All of Serena's prompts can be fully customized.
|
||||
|
||||
@@ -1,12 +1,149 @@
|
||||
# Security Considerations
|
||||
|
||||
As fundamental abilities for a coding agent, Serena contains tools for executing shell commands and modifying files.
|
||||
Therefore, if the respective tool calls are not monitored or restricted (and execution takes place in a sensitive environment),
|
||||
there is a risk of unintended consequences.
|
||||
Security and protection against supply chain attacks are important to us, and we take this topic seriously.
|
||||
|
||||
Therefore, to reduce the risk of unintended consequences when using Serena, it is recommended to
|
||||
* back up your work regularly (e.g. use a version control system like Git),
|
||||
* monitor tool executions carefully (e.g. via your MCP client, provided that it supports it),
|
||||
* consider enabling read-only mode for your project (set `read_only: True` in project.yml) if you only want to analyze code without modifying it,
|
||||
* restrict the set of allowed tools via the [configuration](050_configuration),
|
||||
* use a sandboxed environment for running Serena (e.g. by [using Docker](docker)).
|
||||
Serena comes in two main variants with different security characteristics:
|
||||
|
||||
- the **JetBrains-based variant**, which integrates with a running JetBrains IDE, and
|
||||
- the **language-server-based variant** (the free variant), which can automatically acquire language-server dependencies on demand.
|
||||
|
||||
## JetBrains Variant
|
||||
|
||||
The JetBrains variant of Serena is safe by default.
|
||||
|
||||
At runtime, Serena does **not** download additional components and does **not** start extra helper processes beyond the Serena MCP server itself. It talks to the already running JetBrains IDE and relies on the IDE's own indexing and language intelligence.
|
||||
|
||||
This means that, from a supply-chain perspective, the JetBrains variant has a much smaller runtime attack surface than the language-server-based variant.
|
||||
|
||||
## Language-Server Variant
|
||||
|
||||
The language-server-based variant supports many languages, including languages whose language servers are not typically preinstalled on a machine. For convenience, Serena can therefore download or install certain language-server dependencies on demand.
|
||||
|
||||
We treat this path as security-sensitive and have hardened it accordingly.
|
||||
|
||||
### How Serena Secures Downloaded Language-Server Dependencies
|
||||
|
||||
For language servers that download archives, binaries, VSIX packages, NuGet packages, or other release artifacts, Serena uses a hardened shared download path with the following protections:
|
||||
|
||||
- **Pinned versions by default**: default downloads use exact versions instead of floating `latest` or nightly channels.
|
||||
- **Integrity verification**: downloaded artifacts are checked against pinned SHA256 hashes stored in Serena's source code.
|
||||
- **Host allowlists**: download URLs are restricted to the expected hosts for a given dependency.
|
||||
- **Safe extraction**: archive extraction validates paths to prevent path traversal and zip-slip style attacks.
|
||||
- **Managed install locations**: dependencies are installed into Serena-managed directories instead of into the project repository.
|
||||
|
||||
In practice, this means that a downloaded artifact must match all of the following:
|
||||
|
||||
- the expected version,
|
||||
- the expected host,
|
||||
- the expected SHA256 checksum,
|
||||
- and the expected extraction layout.
|
||||
|
||||
If any of these checks fail, Serena aborts the installation instead of continuing.
|
||||
|
||||
### npm-Based Language Servers
|
||||
|
||||
Some language servers are distributed primarily through npm. For those, Serena currently uses pinned package versions and installs them into Serena-managed directories.
|
||||
|
||||
By default, Serena uses the **user's normal npm configuration**. We do **not** force a registry override unless one is explicitly configured. If needed, both the package version and the registry can be overridden through `ls_specific_settings`.
|
||||
|
||||
For npm-based installs, Serena's current security posture is based on these rules:
|
||||
|
||||
- **Exact package versions are pinned by default**.
|
||||
- **The install location is isolated from the project** and lives in Serena-managed language-server directories.
|
||||
- **The user's npm configuration is trusted by default**.
|
||||
- **Repository and user configuration are assumed to be trusted**.
|
||||
|
||||
This means Serena protects well against accidental version drift, but npm installs still rely on the npm ecosystem and package-manager execution model. In particular, Serena does **not** currently use lockfile-based `npm ci` installs for bundled language-server dependencies.
|
||||
|
||||
### `uvx` and Python Dependency Pinning
|
||||
|
||||
Some parts of Serena rely on `uv` / `uvx`.
|
||||
|
||||
One important detail is that `uvx` ignores the lockfile when installing directly from a Git repository. Because of that, we pin Serena's Python dependencies exactly in `pyproject.toml` so that installations from Git still resolve to exact dependency versions rather than floating ranges.
|
||||
|
||||
For the `ty` Python language server, Serena also uses an exact pinned version when invoking it through `uvx`.
|
||||
|
||||
### Our Assumptions
|
||||
|
||||
The current security model for Serena's language-server variant assumes:
|
||||
|
||||
- the local machine is trusted,
|
||||
- the checked-out repository is trusted,
|
||||
- user configuration is trusted,
|
||||
- package-manager configuration such as npm config is trusted unless explicitly overridden,
|
||||
- and the main risk to defend against is compromised or unexpectedly changing upstream artifacts.
|
||||
|
||||
Under these assumptions, the most important supply-chain protections are:
|
||||
|
||||
- exact version pinning,
|
||||
- hash verification,
|
||||
- host restriction,
|
||||
- and isolated Serena-managed installation directories.
|
||||
|
||||
## Operational Recommendations
|
||||
|
||||
As fundamental abilities for a coding agent, Serena contains tools for executing shell commands and modifying files. Therefore, if the respective tool calls are not monitored or restricted, and execution takes place in a sensitive environment, there is a risk of unintended consequences.
|
||||
|
||||
To reduce that risk, we recommend that you:
|
||||
|
||||
- back up your work regularly, for example by using Git,
|
||||
- monitor tool executions carefully, if your MCP client supports this,
|
||||
- consider enabling read-only mode for analysis-only sessions by setting `read_only: True` in `project.yml`,
|
||||
- restrict the set of allowed tools via the [configuration](050_configuration),
|
||||
- and use a sandboxed environment for running Serena, for example by [using Docker](docker).
|
||||
|
||||
```{dropdown} What Serena Downloads by Default for Language Servers
|
||||
:open:
|
||||
|
||||
Only the language servers listed below download or install additional dependencies automatically by default when the required dependency is missing. Everything else either relies on a system-installed server or on tooling you install separately.
|
||||
|
||||
### Release Artifacts, Archives, or VSIX Packages
|
||||
|
||||
- **AL**: the pinned Microsoft AL VS Code extension (`ms-dynamics-smb.al`) from the VS Code Marketplace.
|
||||
- **C/C++ (`clangd`)**: pinned `clangd` release archives on supported platforms.
|
||||
- **C# (Roslyn LS)**: pinned Roslyn language-server NuGet package for the current platform.
|
||||
- **Clojure**: pinned `clojure-lsp` release artifact.
|
||||
- **Dart**: pinned Dart SDK archive that contains the language server.
|
||||
- **Elixir (`expert`)**: pinned Expert release binary, if not already available locally.
|
||||
- **Groovy**: pinned `vscode-java` runtime bundle used to provide Java for the Groovy LS setup.
|
||||
- **HLSL / shader-language-server**: pinned GitHub release artifacts on supported prebuilt platforms.
|
||||
- **Java (`eclipse.jdt.ls`)**: pinned Gradle distribution, pinned `vscode-java` extension bundle, and pinned IntelliCode VSIX.
|
||||
- **Kotlin**: pinned Kotlin LSP archive.
|
||||
- **Lua**: pinned `lua-language-server` release archive.
|
||||
- **Luau**: pinned `luau-lsp` release archive. In Roblox or standard-doc modes it may also download Luau/Roblox docs and type-definition files.
|
||||
- **Markdown (`marksman`)**: pinned Marksman release binary.
|
||||
- **MATLAB**: the pinned MathWorks MATLAB VS Code extension from the VS Code Marketplace.
|
||||
- **OmniSharp (legacy C# backend)**: pinned OmniSharp and Razor plugin archives.
|
||||
- **Pascal**: pinned Pascal language-server release artifact.
|
||||
- **PHP (`phpactor`)**: pinned `phpactor.phar`.
|
||||
- **PowerShell**: pinned PowerShell Editor Services archive.
|
||||
- **SystemVerilog (`verible`)**: pinned Verible release archive on supported platforms.
|
||||
- **TOML (`taplo`)**: pinned Taplo release artifact.
|
||||
- **Terraform**: pinned `terraform-ls` release archive. The Terraform CLI itself must still already be installed.
|
||||
|
||||
### npm Package Installs
|
||||
|
||||
- **Ansible**: `@ansible/ansible-language-server`
|
||||
- **Bash**: `bash-language-server`
|
||||
- **Elm**: `@elm-tooling/elm-language-server`
|
||||
- **PHP (`intelephense`)**: `intelephense`
|
||||
- **Solidity**: `@nomicfoundation/solidity-language-server`
|
||||
- **TypeScript**: `typescript` and `typescript-language-server`
|
||||
- **Vue**: `@vue/language-server`, plus `typescript` and `typescript-language-server`
|
||||
- **VTSLS**: `@vtsls/language-server`
|
||||
- **YAML**: `yaml-language-server`
|
||||
|
||||
All of the above are installed with exact pinned package versions by default, into Serena-managed directories.
|
||||
|
||||
### Other Package-Manager Based Installs
|
||||
|
||||
- **F#**: installs pinned `fsautocomplete` via `dotnet tool install`.
|
||||
- **Ruby (`ruby-lsp`)**: if not already available through Bundler or as a global executable, Serena installs a pinned `ruby-lsp` gem.
|
||||
- **Python (`ty`)**: launched through `uvx` / `uv x` using an exact pinned `ty` version.
|
||||
- **HLSL on macOS**: if no prebuilt binary is used, Serena builds `shader_language_server` from a pinned version using Cargo.
|
||||
|
||||
### No Automatic Download by Serena
|
||||
|
||||
- **Python (`pyright`)**: Serena uses the locally available Python environment and starts `pyright.langserver` from there.
|
||||
- **Go (`gopls`)**, **Rust (`rust-analyzer`)**, and several other system-tool based integrations expect the language server to be available locally and do not download it automatically.
|
||||
```
|
||||
@@ -1,4 +1,9 @@
|
||||
"""AL Language Server implementation for Microsoft Dynamics 365 Business Central."""
|
||||
"""AL Language Server implementation for Microsoft Dynamics 365 Business Central.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["al"]``:
|
||||
- al_extension_version: Override the pinned AL VS Code extension version
|
||||
downloaded by Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
@@ -7,23 +12,29 @@ import platform
|
||||
import re
|
||||
import stat
|
||||
import time
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from overrides import override
|
||||
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.language_servers.common import quote_windows_path
|
||||
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
AL_EXTENSION_VERSION = "18.0.2242655"
|
||||
AL_EXTENSION_URL = (
|
||||
f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-dynamics-smb/vsextensions/al/{AL_EXTENSION_VERSION}/vspackage"
|
||||
)
|
||||
AL_EXTENSION_SHA256 = "3971995e61a59dc4fcce4a65053072a67991ed624a16635c4f2911f12564b2b9"
|
||||
AL_EXTENSION_ALLOWED_HOSTS = ("marketplace.visualstudio.com",)
|
||||
|
||||
|
||||
class ALLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
@@ -38,6 +49,7 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
- Special initialization sequence required by AL Language Server
|
||||
- Custom AL-specific LSP commands (al/gotodefinition, al/setActiveWorkspace)
|
||||
- File opening requirement before symbol retrieval
|
||||
- `al_extension_version` to override the bundled AL VS Code extension version
|
||||
"""
|
||||
|
||||
# Regex pattern to match AL object names like:
|
||||
@@ -141,46 +153,14 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
try:
|
||||
log.info(f"Downloading AL extension from {url}")
|
||||
|
||||
# Create target directory for the extension
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
# Download with proper headers to mimic VS Code marketplace client
|
||||
# These headers are required for the marketplace to serve the VSIX file
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
"Accept": "application/octet-stream, application/vsix, */*",
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers, stream=True, timeout=300)
|
||||
response.raise_for_status()
|
||||
|
||||
# Save to temporary VSIX file (will be deleted after extraction)
|
||||
temp_file = os.path.join(target_dir, "al_extension_temp.vsix")
|
||||
total_size = int(response.headers.get("content-length", 0))
|
||||
|
||||
log.info(f"Downloading {total_size / 1024 / 1024:.1f} MB...")
|
||||
|
||||
with open(temp_file, "wb") as f:
|
||||
downloaded = 0
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
if total_size > 0 and downloaded % (10 * 1024 * 1024) == 0: # Log progress every 10MB
|
||||
progress = (downloaded / total_size) * 100
|
||||
log.info(f"Download progress: {progress:.1f}%")
|
||||
|
||||
log.info("Download complete, extracting...")
|
||||
|
||||
# Extract VSIX file (VSIX files are just ZIP archives with a different extension)
|
||||
# This will extract the extension folder containing the language server binaries
|
||||
with zipfile.ZipFile(temp_file, "r") as zip_ref:
|
||||
zip_ref.extractall(target_dir)
|
||||
|
||||
# Clean up temp file
|
||||
os.remove(temp_file)
|
||||
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
url,
|
||||
target_dir,
|
||||
"zip",
|
||||
expected_sha256=AL_EXTENSION_SHA256 if url == AL_EXTENSION_URL else None,
|
||||
allowed_hosts=AL_EXTENSION_ALLOWED_HOSTS,
|
||||
)
|
||||
log.info("AL extension extracted successfully")
|
||||
return True
|
||||
|
||||
@@ -276,14 +256,16 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
|
||||
"""
|
||||
al_extension_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "al-extension")
|
||||
al_settings = solidlsp_settings.get_ls_specific_settings(Language.AL)
|
||||
al_extension_version = al_settings.get("al_extension_version", AL_EXTENSION_VERSION)
|
||||
al_extension_url = (
|
||||
"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-dynamics-smb/"
|
||||
f"vsextensions/al/{al_extension_version}/vspackage"
|
||||
)
|
||||
|
||||
# AL extension version - using latest stable version
|
||||
AL_VERSION = "latest"
|
||||
url = f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-dynamics-smb/vsextensions/al/{AL_VERSION}/vspackage"
|
||||
log.info(f"Downloading AL extension from: {al_extension_url}")
|
||||
|
||||
log.info(f"Downloading AL extension from: {url}")
|
||||
|
||||
if cls._download_al_extension(url, al_extension_dir):
|
||||
if cls._download_al_extension(al_extension_url, al_extension_dir):
|
||||
extension_path = os.path.join(al_extension_dir, "extension")
|
||||
if os.path.exists(extension_path):
|
||||
log.info("AL extension downloaded and installed successfully")
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import Any, ClassVar
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -162,13 +162,19 @@ class AnsibleLanguageServer(SolidLanguageServer):
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install Node.js and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
ansible_language_server_version = self._custom_settings.get("ansible_language_server_version", "1.2.3")
|
||||
npm_registry = self._custom_settings.get("npm_registry")
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="ansible-language-server",
|
||||
description="Ansible Language Server (@ansible/ansible-language-server)",
|
||||
command="npm install --prefix ./ @ansible/ansible-language-server@1.2.3",
|
||||
command=build_npm_install_command(
|
||||
"@ansible/ansible-language-server",
|
||||
ansible_language_server_version,
|
||||
npm_registry,
|
||||
),
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,7 +9,7 @@ import pathlib
|
||||
import shutil
|
||||
import threading
|
||||
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
from solidlsp.ls import (
|
||||
DocumentSymbols,
|
||||
LanguageServerDependencyProvider,
|
||||
@@ -58,13 +58,15 @@ class BashLanguageServer(SolidLanguageServer):
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
bash_language_server_version = self._custom_settings.get("bash_language_server_version", "5.6.0")
|
||||
npm_registry = self._custom_settings.get("npm_registry")
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="bash-language-server",
|
||||
description="bash-language-server package",
|
||||
command="npm install --prefix ./ bash-language-server@5.6.0",
|
||||
command=build_npm_install_command("bash-language-server", bash_language_server_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -14,12 +14,20 @@ from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
CLANGD_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
|
||||
|
||||
class ClangdLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++.
|
||||
As the project gets bigger in size, building index will take time. Try running clangd multiple times to ensure index is built properly.
|
||||
Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["cpp"]``:
|
||||
- compile_commands_dir: Directory where Serena writes its transformed
|
||||
``compile_commands.json`` if needed.
|
||||
- clangd_version: Override the pinned Clangd version downloaded by Serena
|
||||
(default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
@@ -125,39 +133,50 @@ class ClangdLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
import shutil
|
||||
|
||||
clangd_version = self._custom_settings.get("clangd_version", "19.1.2")
|
||||
default_version = clangd_version == "19.1.2"
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="Clangd",
|
||||
description="Clangd for Linux (x64)",
|
||||
url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-linux-19.1.2.zip",
|
||||
url=f"https://github.com/clangd/clangd/releases/download/{clangd_version}/clangd-linux-{clangd_version}.zip",
|
||||
platform_id="linux-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clangd_19.1.2/bin/clangd",
|
||||
binary_name=f"clangd_{clangd_version}/bin/clangd",
|
||||
sha256="7c09614eff857d590e4502ef516f035ff94cfb8b795de14ece5afbc53a206caf" if default_version else None,
|
||||
allowed_hosts=CLANGD_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="Clangd",
|
||||
description="Clangd for Windows (x64)",
|
||||
url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-windows-19.1.2.zip",
|
||||
url=f"https://github.com/clangd/clangd/releases/download/{clangd_version}/clangd-windows-{clangd_version}.zip",
|
||||
platform_id="win-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clangd_19.1.2/bin/clangd.exe",
|
||||
binary_name=f"clangd_{clangd_version}/bin/clangd.exe",
|
||||
sha256="5b6ceb0f85d63fa0c2c9aab31c29bebd41dc11da1f160ef21bc2fea93270a20d" if default_version else None,
|
||||
allowed_hosts=CLANGD_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="Clangd",
|
||||
description="Clangd for macOS (x64)",
|
||||
url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip",
|
||||
url=f"https://github.com/clangd/clangd/releases/download/{clangd_version}/clangd-mac-{clangd_version}.zip",
|
||||
platform_id="osx-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clangd_19.1.2/bin/clangd",
|
||||
binary_name=f"clangd_{clangd_version}/bin/clangd",
|
||||
sha256="d3b329b3f58602c57ca6501d255147af1bccad3691b1cb0c12c258fcd2da1be3" if default_version else None,
|
||||
allowed_hosts=CLANGD_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="Clangd",
|
||||
description="Clangd for macOS (Arm64)",
|
||||
url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip",
|
||||
url=f"https://github.com/clangd/clangd/releases/download/{clangd_version}/clangd-mac-{clangd_version}.zip",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="clangd_19.1.2/bin/clangd",
|
||||
binary_name=f"clangd_{clangd_version}/bin/clangd",
|
||||
sha256="d3b329b3f58602c57ca6501d255147af1bccad3691b1cb0c12c258fcd2da1be3" if default_version else None,
|
||||
allowed_hosts=CLANGD_ALLOWED_HOSTS,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -19,6 +19,13 @@ from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
CLOJURE_LSP_VERSION = "2026.02.20-16.08.58"
|
||||
CLOJURE_LSP_ALLOWED_HOSTS = (
|
||||
"github.com",
|
||||
"release-assets.githubusercontent.com",
|
||||
"objects.githubusercontent.com",
|
||||
)
|
||||
|
||||
|
||||
def run_command(cmd: list, capture_output: bool = True) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
@@ -42,49 +49,69 @@ def verify_clojure_cli() -> None:
|
||||
|
||||
class ClojureLSP(SolidLanguageServer):
|
||||
"""
|
||||
Provides a clojure-lsp specific instantiation of the LanguageServer class. Contains various configurations and settings specific to clojure.
|
||||
Provides a clojure-lsp specific instantiation of the LanguageServer class.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["clojure"]``:
|
||||
- clojure_lsp_version: Override the pinned clojure-lsp version downloaded
|
||||
by Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
clojure_lsp_releases = "https://github.com/clojure-lsp/clojure-lsp/releases/latest/download"
|
||||
runtime_dependencies = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-aarch64.zip",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-amd64.zip",
|
||||
platform_id="osx-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-aarch64.zip",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-amd64.zip",
|
||||
platform_id="linux-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-windows-amd64.zip",
|
||||
platform_id="win-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp.exe",
|
||||
),
|
||||
]
|
||||
)
|
||||
CLOJURE_LSP_VERSION = CLOJURE_LSP_VERSION
|
||||
CLOJURE_LSP_ALLOWED_HOSTS = CLOJURE_LSP_ALLOWED_HOSTS
|
||||
|
||||
@classmethod
|
||||
def _runtime_dependencies(cls, version: str) -> RuntimeDependencyCollection:
|
||||
clojure_lsp_releases = f"https://github.com/clojure-lsp/clojure-lsp/releases/download/{version}"
|
||||
default_version = version == cls.CLOJURE_LSP_VERSION
|
||||
return RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-aarch64.zip",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp",
|
||||
sha256="a14d4db074f665378214e2dc888472e186c228dfa065c777b0534bfda5571669" if default_version else None,
|
||||
allowed_hosts=CLOJURE_LSP_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-amd64.zip",
|
||||
platform_id="osx-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp",
|
||||
sha256="5507434c27104ab816e096d3336d8191641de8a65b57d76afb585d07167a3cf2" if default_version else None,
|
||||
allowed_hosts=CLOJURE_LSP_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-aarch64.zip",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp",
|
||||
sha256="f8f09fa07dd4b6743b5c57270ccf1ee5cdbc5fca09dbca8b6a3b22705b5da4e1" if default_version else None,
|
||||
allowed_hosts=CLOJURE_LSP_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-amd64.zip",
|
||||
platform_id="linux-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp",
|
||||
sha256="52e8bf4fd4cf171df0a3077c8bb5a3bf598d4c621e94b4876dab943a61267309" if default_version else None,
|
||||
allowed_hosts=CLOJURE_LSP_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="clojure-lsp",
|
||||
url=f"{clojure_lsp_releases}/clojure-lsp-native-windows-amd64.zip",
|
||||
platform_id="win-x64",
|
||||
archive_type="zip",
|
||||
binary_name="clojure-lsp.exe",
|
||||
sha256="817b1271288817c954fb9e595278b1f25003827ce31f8785f253dc4ac911041f" if default_version else None,
|
||||
allowed_hosts=CLOJURE_LSP_ALLOWED_HOSTS,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
"""
|
||||
@@ -109,7 +136,8 @@ class ClojureLSP(SolidLanguageServer):
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""Setup runtime dependencies for clojure-lsp and return the path to the executable."""
|
||||
verify_clojure_cli()
|
||||
deps = ClojureLSP.runtime_dependencies
|
||||
clojure_lsp_version = self._custom_settings.get("clojure_lsp_version", ClojureLSP.CLOJURE_LSP_VERSION)
|
||||
deps = ClojureLSP._runtime_dependencies(clojure_lsp_version)
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
|
||||
clojurelsp_executable_path = deps.binary_path(self._ls_resources_dir)
|
||||
|
||||
@@ -21,6 +21,8 @@ class RuntimeDependency:
|
||||
id: str
|
||||
platform_id: str | None = None
|
||||
url: str | None = None
|
||||
sha256: str | None = None
|
||||
allowed_hosts: tuple[str, ...] | list[str] | None = None
|
||||
archive_type: str | None = None
|
||||
binary_name: str | None = None
|
||||
command: str | list[str] | None = None
|
||||
@@ -136,9 +138,30 @@ class RuntimeDependencyCollection:
|
||||
|
||||
if dep.archive_type in ("gz", "binary") and dep.binary_name:
|
||||
dest = os.path.join(target_dir, dep.binary_name)
|
||||
FileUtils.download_and_extract_archive(dep.url, dest, dep.archive_type)
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
dep.url,
|
||||
dest,
|
||||
dep.archive_type,
|
||||
expected_sha256=dep.sha256,
|
||||
allowed_hosts=dep.allowed_hosts,
|
||||
)
|
||||
else:
|
||||
FileUtils.download_and_extract_archive(dep.url, target_dir, dep.archive_type or "zip")
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
dep.url,
|
||||
target_dir,
|
||||
dep.archive_type or "zip",
|
||||
expected_sha256=dep.sha256,
|
||||
allowed_hosts=dep.allowed_hosts,
|
||||
)
|
||||
|
||||
|
||||
def build_npm_install_command(package_name: str, version: str, registry: str | None = None) -> list[str]:
|
||||
"""Build a pinned npm install command for a package in a Serena-managed install directory."""
|
||||
command = ["npm", "install", "--prefix", "./"]
|
||||
if registry:
|
||||
command.extend(["--registry", registry])
|
||||
command.append(f"{package_name}@{version}")
|
||||
return command
|
||||
|
||||
|
||||
def quote_windows_path(path: str) -> str:
|
||||
|
||||
@@ -7,8 +7,8 @@ import os
|
||||
import platform
|
||||
import shutil
|
||||
import threading
|
||||
import urllib.request
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
@@ -19,85 +19,117 @@ from solidlsp.ls import DocumentSymbols, LanguageServerDependencyProvider, LSPFi
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_types import Hover, UnifiedSymbolInformation
|
||||
from solidlsp.ls_utils import PathUtils
|
||||
from solidlsp.ls_utils import FileUtils, PathUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, InitializeResult
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from solidlsp.util.zip import SafeZipExtractor
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
NUGET_ALLOWED_HOSTS = ("www.nuget.org", "nuget.org", "globalcdn.nuget.org")
|
||||
DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION = "5.5.0-2.26078.4"
|
||||
|
||||
_RUNTIME_DEPENDENCIES = [
|
||||
RuntimeDependency(
|
||||
id="CSharpLanguageServer",
|
||||
description="Roslyn Language Server for Windows (x64)",
|
||||
package_name="roslyn-language-server.win-x64",
|
||||
package_version="5.5.0-2.26078.4",
|
||||
url="https://www.nuget.org/api/v2/package/roslyn-language-server.win-x64/5.5.0-2.26078.4",
|
||||
package_version=DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION,
|
||||
url=f"https://www.nuget.org/api/v2/package/roslyn-language-server.win-x64/{DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION}",
|
||||
platform_id="win-x64",
|
||||
archive_type="nupkg",
|
||||
binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
|
||||
extract_path="tools/net10.0/win-x64",
|
||||
sha256="7f3d4119e75305399e6faa81a68240b33c48b94ad523a904594abd00db95572a",
|
||||
allowed_hosts=NUGET_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="CSharpLanguageServer",
|
||||
description="Roslyn Language Server for Windows (ARM64)",
|
||||
package_name="roslyn-language-server.win-arm64",
|
||||
package_version="5.5.0-2.26078.4",
|
||||
url="https://www.nuget.org/api/v2/package/roslyn-language-server.win-arm64/5.5.0-2.26078.4",
|
||||
package_version=DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION,
|
||||
url=f"https://www.nuget.org/api/v2/package/roslyn-language-server.win-arm64/{DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION}",
|
||||
platform_id="win-arm64",
|
||||
archive_type="nupkg",
|
||||
binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
|
||||
extract_path="tools/net10.0/win-arm64",
|
||||
sha256="0fe3381c4340a7494a5242c3d0c8be1af6ef0802de8b458f947cebca76fd26bc",
|
||||
allowed_hosts=NUGET_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="CSharpLanguageServer",
|
||||
description="Roslyn Language Server for macOS (x64)",
|
||||
package_name="roslyn-language-server.osx-x64",
|
||||
package_version="5.5.0-2.26078.4",
|
||||
url="https://www.nuget.org/api/v2/package/roslyn-language-server.osx-x64/5.5.0-2.26078.4",
|
||||
package_version=DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION,
|
||||
url=f"https://www.nuget.org/api/v2/package/roslyn-language-server.osx-x64/{DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION}",
|
||||
platform_id="osx-x64",
|
||||
archive_type="nupkg",
|
||||
binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
|
||||
extract_path="tools/net10.0/osx-x64",
|
||||
sha256="c8de61a88c65150e12f561a2659f70b59d27a7465865136a1de950d2ef826c6d",
|
||||
allowed_hosts=NUGET_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="CSharpLanguageServer",
|
||||
description="Roslyn Language Server for macOS (ARM64)",
|
||||
package_name="roslyn-language-server.osx-arm64",
|
||||
package_version="5.5.0-2.26078.4",
|
||||
url="https://www.nuget.org/api/v2/package/roslyn-language-server.osx-arm64/5.5.0-2.26078.4",
|
||||
package_version=DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION,
|
||||
url=f"https://www.nuget.org/api/v2/package/roslyn-language-server.osx-arm64/{DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION}",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="nupkg",
|
||||
binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
|
||||
extract_path="tools/net10.0/osx-arm64",
|
||||
sha256="995207c14e01dafa71e84080a7eb1f045a697b0c3bb468077bb3809b69bdf456",
|
||||
allowed_hosts=NUGET_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="CSharpLanguageServer",
|
||||
description="Roslyn Language Server for Linux (x64)",
|
||||
package_name="roslyn-language-server.linux-x64",
|
||||
package_version="5.5.0-2.26078.4",
|
||||
url="https://www.nuget.org/api/v2/package/roslyn-language-server.linux-x64/5.5.0-2.26078.4",
|
||||
package_version=DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION,
|
||||
url=f"https://www.nuget.org/api/v2/package/roslyn-language-server.linux-x64/{DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION}",
|
||||
platform_id="linux-x64",
|
||||
archive_type="nupkg",
|
||||
binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
|
||||
extract_path="tools/net10.0/linux-x64",
|
||||
sha256="1aad25de456d637a1eee993ca0d569a1b78d711744ccb36410a3a20250a48aa6",
|
||||
allowed_hosts=NUGET_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="CSharpLanguageServer",
|
||||
description="Roslyn Language Server for Linux (ARM64)",
|
||||
package_name="roslyn-language-server.linux-arm64",
|
||||
package_version="5.5.0-2.26078.4",
|
||||
url="https://www.nuget.org/api/v2/package/roslyn-language-server.linux-arm64/5.5.0-2.26078.4",
|
||||
package_version=DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION,
|
||||
url=f"https://www.nuget.org/api/v2/package/roslyn-language-server.linux-arm64/{DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION}",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="nupkg",
|
||||
binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
|
||||
extract_path="tools/net10.0/linux-arm64",
|
||||
sha256="a7dd49bbc0e25d0e2968ae31ec5f3c774373866db51f3500fcea0ac320e2bbc1",
|
||||
allowed_hosts=NUGET_ALLOWED_HOSTS,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _runtime_dependencies_for_version(version: str) -> list[RuntimeDependency]:
|
||||
"""Return the Roslyn LS runtime dependencies for the configured version."""
|
||||
default_version = version == DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION
|
||||
result: list[RuntimeDependency] = []
|
||||
for dependency in _RUNTIME_DEPENDENCIES:
|
||||
assert dependency.package_version is not None
|
||||
assert dependency.url is not None
|
||||
result.append(
|
||||
replace(
|
||||
dependency,
|
||||
package_version=version,
|
||||
url=dependency.url.replace(dependency.package_version, version),
|
||||
sha256=dependency.sha256 if default_version else None,
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def breadth_first_file_scan(root_dir: str) -> Iterable[str]:
|
||||
"""
|
||||
Perform a breadth-first scan of files in the given directory.
|
||||
@@ -151,6 +183,9 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
This is a list of dicts, each containing at least the "id" key, and optionally "platform_id" to uniquely
|
||||
identify the dependency to override.
|
||||
|
||||
You can also set `csharp_language_server_version` in ``ls_specific_settings["csharp"]`` to override
|
||||
the pinned Roslyn Language Server package version Serena downloads by default.
|
||||
|
||||
Example - Override Roslyn Language Server URL:
|
||||
```
|
||||
{
|
||||
@@ -347,9 +382,12 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
filtered_overrides.append(dep_override)
|
||||
|
||||
log.debug("Resolving runtime dependencies")
|
||||
csharp_language_server_version = self._custom_settings.get(
|
||||
"csharp_language_server_version", DEFAULT_CSHARP_LANGUAGE_SERVER_VERSION
|
||||
)
|
||||
|
||||
runtime_dependencies = RuntimeDependencyCollection(
|
||||
_RUNTIME_DEPENDENCIES,
|
||||
_runtime_dependencies_for_version(csharp_language_server_version),
|
||||
overrides=filtered_overrides,
|
||||
)
|
||||
|
||||
@@ -433,27 +471,19 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
if url is None:
|
||||
raise SolidLSPException(f"No URL specified for package {package_name} version {package_version}")
|
||||
|
||||
# Create temporary directory for package download
|
||||
temp_dir = Path(self._ls_resources_dir) / "temp_downloads"
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
log.debug(f"Downloading package from: {url}")
|
||||
|
||||
# Download the .nupkg file
|
||||
nupkg_file = temp_dir / f"{package_name}.{package_version}.nupkg"
|
||||
urllib.request.urlretrieve(url, nupkg_file)
|
||||
|
||||
# Extract the .nupkg file (it's just a zip file)
|
||||
package_extract_dir = temp_dir / f"{package_name}.{package_version}"
|
||||
package_extract_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Use SafeZipExtractor to handle long paths and skip errors
|
||||
extractor = SafeZipExtractor(archive_path=nupkg_file, extract_dir=package_extract_dir, verbose=False)
|
||||
extractor.extract_all()
|
||||
|
||||
# Clean up the nupkg file
|
||||
nupkg_file.unlink()
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
url,
|
||||
str(package_extract_dir),
|
||||
"zip",
|
||||
expected_sha256=dependency.sha256,
|
||||
allowed_hosts=dependency.allowed_hosts,
|
||||
)
|
||||
|
||||
log.info(f"Successfully downloaded and extracted {package_name} version {package_version} from NuGet.org")
|
||||
return package_extract_dir
|
||||
|
||||
@@ -7,16 +7,22 @@ from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from ..ls_config import LanguageServerConfig
|
||||
from ..ls_config import Language, LanguageServerConfig
|
||||
from ..lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DART_ALLOWED_HOSTS = ("storage.googleapis.com",)
|
||||
|
||||
|
||||
class DartLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["dart"]``:
|
||||
- dart_sdk_version: Override the pinned Dart SDK version downloaded by Serena
|
||||
(default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
|
||||
@@ -30,47 +36,59 @@ class DartLanguageServer(SolidLanguageServer):
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
dart_settings = solidlsp_settings.get_ls_specific_settings(Language.DART)
|
||||
dart_sdk_version = dart_settings.get("dart_sdk_version", "3.7.1")
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="DartLanguageServer",
|
||||
description="Dart Language Server for Linux (x64)",
|
||||
url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-linux-x64-release.zip",
|
||||
url=f"https://storage.googleapis.com/dart-archive/channels/stable/release/{dart_sdk_version}/sdk/dartsdk-linux-x64-release.zip",
|
||||
platform_id="linux-x64",
|
||||
archive_type="zip",
|
||||
binary_name="dart-sdk/bin/dart",
|
||||
sha256="2813959e7d9650334015b927cc533f5beadfbf7fa48248beec471f8942a0ee71" if dart_sdk_version == "3.7.1" else None,
|
||||
allowed_hosts=DART_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="DartLanguageServer",
|
||||
description="Dart Language Server for Windows (x64)",
|
||||
url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-x64-release.zip",
|
||||
url=f"https://storage.googleapis.com/dart-archive/channels/stable/release/{dart_sdk_version}/sdk/dartsdk-windows-x64-release.zip",
|
||||
platform_id="win-x64",
|
||||
archive_type="zip",
|
||||
binary_name="dart-sdk/bin/dart.exe",
|
||||
sha256="f56c03122e17abe5be1429eee0a975fb8ed511b6731ec90c6475992d3dee4ea5" if dart_sdk_version == "3.7.1" else None,
|
||||
allowed_hosts=DART_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="DartLanguageServer",
|
||||
description="Dart Language Server for Windows (arm64)",
|
||||
url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-arm64-release.zip",
|
||||
url=f"https://storage.googleapis.com/dart-archive/channels/stable/release/{dart_sdk_version}/sdk/dartsdk-windows-arm64-release.zip",
|
||||
platform_id="win-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="dart-sdk/bin/dart.exe",
|
||||
sha256="fada411c6538d0ac24c35d6360767241f1298f64cbc5e88716387d54757a105a" if dart_sdk_version == "3.7.1" else None,
|
||||
allowed_hosts=DART_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="DartLanguageServer",
|
||||
description="Dart Language Server for macOS (x64)",
|
||||
url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-x64-release.zip",
|
||||
url=f"https://storage.googleapis.com/dart-archive/channels/stable/release/{dart_sdk_version}/sdk/dartsdk-macos-x64-release.zip",
|
||||
platform_id="osx-x64",
|
||||
archive_type="zip",
|
||||
binary_name="dart-sdk/bin/dart",
|
||||
sha256="a2765917b6ae49d1ac119553df9584989f9c441a46e8f18c129ba52489658d2e" if dart_sdk_version == "3.7.1" else None,
|
||||
allowed_hosts=DART_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="DartLanguageServer",
|
||||
description="Dart Language Server for macOS (arm64)",
|
||||
url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-arm64-release.zip",
|
||||
url=f"https://storage.googleapis.com/dart-archive/channels/stable/release/{dart_sdk_version}/sdk/dartsdk-macos-arm64-release.zip",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="dart-sdk/bin/dart",
|
||||
sha256="f57c25163092bac818f8ca6250a0d8b2c56344c6a075a1bd7c60da7ac28b32a4" if dart_sdk_version == "3.7.1" else None,
|
||||
allowed_hosts=DART_ALLOWED_HOSTS,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -25,6 +25,23 @@ from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
GRADLE_ALLOWED_HOSTS = ("services.gradle.org", "github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
GRADLE_SHA256 = "7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999"
|
||||
VSCODE_JAVA_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
VSCODE_JAVA_SHA256_BY_PLATFORM = {
|
||||
"osx-arm64": "bc00c2699d4b8d478eb9a1621db9d6d3a12ea0dcc247a9cd8040e8ac19c03933",
|
||||
"osx-x64": "03ae1db1a22c15561a620f1b722d6797d35d4faaa7c4666dbe6ca2715089852f",
|
||||
"linux-arm64": "e15bc9b2a665d3453203402621b5441062aa41b0ec2d140661f439326fd248c1",
|
||||
"linux-x64": "7660b7b527be6fda46a917966b34d828e7416d5cc84287b29b88e7b99c1737f9",
|
||||
"win-x64": "ef195b45bd260976ad2e84618f4044b5d7248deed41d647573f0ee22c4233df3",
|
||||
}
|
||||
INTELLICODE_ALLOWED_HOSTS = (
|
||||
"visualstudioexptteam.gallery.vsassets.io",
|
||||
"marketplace.visualstudio.com",
|
||||
"download.visualstudio.microsoft.com",
|
||||
)
|
||||
INTELLICODE_SHA256 = "7f61a7f96d101cdf230f96821be3fddd8f890ebfefb3695d18beee43004ae251"
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class RuntimeDependencyPaths:
|
||||
@@ -52,6 +69,9 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
- gradle_wrapper_enabled: Whether to use the project's Gradle wrapper (default: false)
|
||||
- gradle_java_home: Path to JDK for Gradle (default: null, uses bundled JRE)
|
||||
- use_system_java_home: Whether to use the system's JAVA_HOME for JDTLS itself (default: false)
|
||||
- gradle_version: Override the pinned Gradle distribution version downloaded by Serena
|
||||
- vscode_java_version: Override the pinned vscode-java runtime bundle version downloaded by Serena
|
||||
- intellicode_version: Override the pinned IntelliCode VSIX version downloaded by Serena
|
||||
|
||||
Example configuration in ~/.serena/serena_config.yml:
|
||||
```yaml
|
||||
@@ -64,6 +84,9 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
gradle_wrapper_enabled: true # set to true for projects with custom plugins/repositories
|
||||
gradle_java_home: "/path/to/jdk" # set to override Gradle's JDK
|
||||
use_system_java_home: true # set to true to use system JAVA_HOME for JDTLS
|
||||
gradle_version: "8.14.2"
|
||||
vscode_java_version: "1.42.0-561"
|
||||
intellicode_version: "1.2.30"
|
||||
```
|
||||
"""
|
||||
|
||||
@@ -115,33 +138,48 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
super().__init__(custom_settings, ls_resources_dir)
|
||||
self._solidlsp_settings = solidlsp_settings
|
||||
self._repository_root_path = repository_root_path
|
||||
self.runtime_dependency_paths = self._setup_runtime_dependencies(ls_resources_dir)
|
||||
self.runtime_dependency_paths = self._setup_runtime_dependencies(ls_resources_dir, custom_settings)
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, ls_resources_dir: str) -> RuntimeDependencyPaths:
|
||||
@staticmethod
|
||||
def _setup_runtime_dependencies(
|
||||
ls_resources_dir: str, custom_settings: SolidLSPSettings.CustomLSSettings
|
||||
) -> RuntimeDependencyPaths:
|
||||
"""
|
||||
Setup runtime dependencies for EclipseJDTLS and return the paths.
|
||||
"""
|
||||
platformId = PlatformUtils.get_platform_id()
|
||||
gradle_version = custom_settings.get("gradle_version", "8.14.2")
|
||||
vscode_java_version = custom_settings.get("vscode_java_version", "1.42.0-561")
|
||||
vscode_java_tag = f"v{vscode_java_version.rsplit('-', 1)[0]}"
|
||||
intellicode_version = custom_settings.get("intellicode_version", "1.2.30")
|
||||
default_gradle_version = gradle_version == "8.14.2"
|
||||
default_vscode_java_version = vscode_java_version == "1.42.0-561"
|
||||
default_intellicode_version = intellicode_version == "1.2.30"
|
||||
|
||||
runtime_dependencies = {
|
||||
runtime_dependencies: dict[str, dict[str, dict[str, object]]] = {
|
||||
"gradle": {
|
||||
"platform-agnostic": {
|
||||
"url": "https://services.gradle.org/distributions/gradle-8.14.2-bin.zip",
|
||||
"url": f"https://services.gradle.org/distributions/gradle-{gradle_version}-bin.zip",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": ".",
|
||||
"sha256": GRADLE_SHA256 if default_gradle_version else None,
|
||||
"allowed_hosts": GRADLE_ALLOWED_HOSTS,
|
||||
}
|
||||
},
|
||||
"vscode-java": {
|
||||
"darwin-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-arm64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-arm64"] if default_vscode_java_version else None,
|
||||
"allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS,
|
||||
},
|
||||
"osx-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-arm64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-arm64"] if default_vscode_java_version else None,
|
||||
"allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS,
|
||||
"jre_home_path": "extension/jre/21.0.7-macosx-aarch64",
|
||||
"jre_path": "extension/jre/21.0.7-macosx-aarch64/bin/java",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
@@ -149,9 +187,11 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"jdtls_readonly_config_path": "extension/server/config_mac_arm",
|
||||
},
|
||||
"osx-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-x64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-x64"] if default_vscode_java_version else None,
|
||||
"allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS,
|
||||
"jre_home_path": "extension/jre/21.0.7-macosx-x86_64",
|
||||
"jre_path": "extension/jre/21.0.7-macosx-x86_64/bin/java",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
@@ -159,9 +199,11 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"jdtls_readonly_config_path": "extension/server/config_mac",
|
||||
},
|
||||
"linux-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-linux-arm64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["linux-arm64"] if default_vscode_java_version else None,
|
||||
"allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS,
|
||||
"jre_home_path": "extension/jre/21.0.7-linux-aarch64",
|
||||
"jre_path": "extension/jre/21.0.7-linux-aarch64/bin/java",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
@@ -169,9 +211,11 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"jdtls_readonly_config_path": "extension/server/config_linux_arm",
|
||||
},
|
||||
"linux-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-linux-x64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["linux-x64"] if default_vscode_java_version else None,
|
||||
"allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS,
|
||||
"jre_home_path": "extension/jre/21.0.7-linux-x86_64",
|
||||
"jre_path": "extension/jre/21.0.7-linux-x86_64/bin/java",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
@@ -179,9 +223,11 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"jdtls_readonly_config_path": "extension/server/config_linux",
|
||||
},
|
||||
"win-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-win32-x64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["win-x64"] if default_vscode_java_version else None,
|
||||
"allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS,
|
||||
"jre_home_path": "extension/jre/21.0.7-win32-x86_64",
|
||||
"jre_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
@@ -191,10 +237,12 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
},
|
||||
"intellicode": {
|
||||
"platform-agnostic": {
|
||||
"url": "https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/1.2.30/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage",
|
||||
"alternate_url": "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/VisualStudioExptTeam/vsextensions/vscodeintellicode/1.2.30/vspackage",
|
||||
"url": f"https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/{intellicode_version}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage",
|
||||
"alternate_url": f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/VisualStudioExptTeam/vsextensions/vscodeintellicode/{intellicode_version}/vspackage",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "intellicode",
|
||||
"sha256": INTELLICODE_SHA256 if default_intellicode_version else None,
|
||||
"allowed_hosts": INTELLICODE_ALLOWED_HOSTS,
|
||||
"intellicode_jar_path": "extension/dist/com.microsoft.jdtls.intellicode.core-0.7.0.jar",
|
||||
"intellisense_members_path": "extension/dist/bundledModels/java_intellisense-members",
|
||||
}
|
||||
@@ -204,27 +252,30 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
gradle_path = str(
|
||||
PurePath(
|
||||
ls_resources_dir,
|
||||
"gradle-8.14.2",
|
||||
f"gradle-{gradle_version}",
|
||||
)
|
||||
)
|
||||
|
||||
if not os.path.exists(gradle_path):
|
||||
FileUtils.download_and_extract_archive(
|
||||
runtime_dependencies["gradle"]["platform-agnostic"]["url"],
|
||||
gradle_dependency = runtime_dependencies["gradle"]["platform-agnostic"]
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
cast(str, gradle_dependency["url"]),
|
||||
str(PurePath(gradle_path).parent),
|
||||
runtime_dependencies["gradle"]["platform-agnostic"]["archiveType"],
|
||||
cast(str, gradle_dependency["archiveType"]),
|
||||
expected_sha256=cast(str | None, gradle_dependency["sha256"]),
|
||||
allowed_hosts=cast(tuple[str, ...], gradle_dependency["allowed_hosts"]),
|
||||
)
|
||||
|
||||
assert os.path.exists(gradle_path)
|
||||
|
||||
dependency = runtime_dependencies["vscode-java"][platformId.value]
|
||||
vscode_java_path = str(PurePath(ls_resources_dir, dependency["relative_extraction_path"]))
|
||||
vscode_java_path = str(PurePath(ls_resources_dir, cast(str, dependency["relative_extraction_path"])))
|
||||
os.makedirs(vscode_java_path, exist_ok=True)
|
||||
jre_home_path = str(PurePath(vscode_java_path, dependency["jre_home_path"]))
|
||||
jre_path = str(PurePath(vscode_java_path, dependency["jre_path"]))
|
||||
lombok_jar_path = str(PurePath(vscode_java_path, dependency["lombok_jar_path"]))
|
||||
jdtls_launcher_jar_path = str(PurePath(vscode_java_path, dependency["jdtls_launcher_jar_path"]))
|
||||
jdtls_readonly_config_path = str(PurePath(vscode_java_path, dependency["jdtls_readonly_config_path"]))
|
||||
jre_home_path = str(PurePath(vscode_java_path, cast(str, dependency["jre_home_path"])))
|
||||
jre_path = str(PurePath(vscode_java_path, cast(str, dependency["jre_path"])))
|
||||
lombok_jar_path = str(PurePath(vscode_java_path, cast(str, dependency["lombok_jar_path"])))
|
||||
jdtls_launcher_jar_path = str(PurePath(vscode_java_path, cast(str, dependency["jdtls_launcher_jar_path"])))
|
||||
jdtls_readonly_config_path = str(PurePath(vscode_java_path, cast(str, dependency["jdtls_readonly_config_path"])))
|
||||
if not all(
|
||||
[
|
||||
os.path.exists(vscode_java_path),
|
||||
@@ -235,7 +286,13 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
os.path.exists(jdtls_readonly_config_path),
|
||||
]
|
||||
):
|
||||
FileUtils.download_and_extract_archive(dependency["url"], vscode_java_path, dependency["archiveType"])
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
cast(str, dependency["url"]),
|
||||
vscode_java_path,
|
||||
cast(str, dependency["archiveType"]),
|
||||
expected_sha256=cast(str | None, dependency["sha256"]),
|
||||
allowed_hosts=cast(tuple[str, ...], dependency["allowed_hosts"]),
|
||||
)
|
||||
|
||||
os.chmod(jre_path, 0o755)
|
||||
|
||||
@@ -247,10 +304,10 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
assert os.path.exists(jdtls_readonly_config_path)
|
||||
|
||||
dependency = runtime_dependencies["intellicode"]["platform-agnostic"]
|
||||
intellicode_directory_path = str(PurePath(ls_resources_dir, dependency["relative_extraction_path"]))
|
||||
intellicode_directory_path = str(PurePath(ls_resources_dir, cast(str, dependency["relative_extraction_path"])))
|
||||
os.makedirs(intellicode_directory_path, exist_ok=True)
|
||||
intellicode_jar_path = str(PurePath(intellicode_directory_path, dependency["intellicode_jar_path"]))
|
||||
intellisense_members_path = str(PurePath(intellicode_directory_path, dependency["intellisense_members_path"]))
|
||||
intellicode_jar_path = str(PurePath(intellicode_directory_path, cast(str, dependency["intellicode_jar_path"])))
|
||||
intellisense_members_path = str(PurePath(intellicode_directory_path, cast(str, dependency["intellisense_members_path"])))
|
||||
if not all(
|
||||
[
|
||||
os.path.exists(intellicode_directory_path),
|
||||
@@ -258,7 +315,13 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
os.path.exists(intellisense_members_path),
|
||||
]
|
||||
):
|
||||
FileUtils.download_and_extract_archive(dependency["url"], intellicode_directory_path, dependency["archiveType"])
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
cast(str, dependency["url"]),
|
||||
intellicode_directory_path,
|
||||
cast(str, dependency["archiveType"]),
|
||||
expected_sha256=cast(str | None, dependency["sha256"]),
|
||||
allowed_hosts=cast(tuple[str, ...], dependency["allowed_hosts"]),
|
||||
)
|
||||
|
||||
assert os.path.exists(intellicode_directory_path)
|
||||
assert os.path.exists(intellicode_jar_path)
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Any, cast
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -19,10 +19,21 @@ from ..common import RuntimeDependency
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
EXPERT_VERSION = "v0.1.0-rc.6"
|
||||
EXPERT_ALLOWED_HOSTS = (
|
||||
"github.com",
|
||||
"release-assets.githubusercontent.com",
|
||||
"objects.githubusercontent.com",
|
||||
)
|
||||
|
||||
|
||||
class ElixirTools(SolidLanguageServer):
|
||||
"""
|
||||
Provides Elixir specific instantiation of the LanguageServer class using Expert, the official Elixir language server.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["elixir"]``:
|
||||
- expert_version: Override the pinned Expert version downloaded by Serena
|
||||
(default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
@override
|
||||
@@ -66,6 +77,8 @@ class ElixirTools(SolidLanguageServer):
|
||||
Setup runtime dependencies for Expert.
|
||||
Downloads the Expert binary for the current platform and returns the path to the executable.
|
||||
"""
|
||||
elixir_settings = solidlsp_settings.get_ls_specific_settings(Language.ELIXIR)
|
||||
expert_version = elixir_settings.get("expert_version", EXPERT_VERSION)
|
||||
# Check if Elixir is available first
|
||||
elixir_version = cls._get_elixir_version()
|
||||
if not elixir_version:
|
||||
@@ -91,63 +104,62 @@ class ElixirTools(SolidLanguageServer):
|
||||
PlatformId.OSX_x64,
|
||||
PlatformId.OSX_arm64,
|
||||
PlatformId.WIN_x64,
|
||||
PlatformId.WIN_arm64,
|
||||
]
|
||||
assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for Expert at the moment"
|
||||
|
||||
expert_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "expert")
|
||||
|
||||
EXPERT_VERSION = "nightly"
|
||||
|
||||
# Define runtime dependencies inline
|
||||
runtime_deps = {
|
||||
PlatformId.LINUX_x64: RuntimeDependency(
|
||||
id="expert_linux_amd64",
|
||||
platform_id="linux-x64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{EXPERT_VERSION}/expert_linux_amd64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{expert_version}/expert_linux_amd64",
|
||||
archive_type="binary",
|
||||
binary_name="expert_linux_amd64",
|
||||
extract_path="expert",
|
||||
sha256="643a492ff972246668b0ca356a84c3d0a0f5feeae0ab5dc1b9a126876ed460e4" if expert_version == EXPERT_VERSION else None,
|
||||
allowed_hosts=EXPERT_ALLOWED_HOSTS,
|
||||
),
|
||||
PlatformId.LINUX_arm64: RuntimeDependency(
|
||||
id="expert_linux_arm64",
|
||||
platform_id="linux-arm64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{EXPERT_VERSION}/expert_linux_arm64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{expert_version}/expert_linux_arm64",
|
||||
archive_type="binary",
|
||||
binary_name="expert_linux_arm64",
|
||||
extract_path="expert",
|
||||
sha256="d8b830bdaa8991d7ebf255dacbb3674f3ea335c87d0bfba4b7f907ded4a8f014" if expert_version == EXPERT_VERSION else None,
|
||||
allowed_hosts=EXPERT_ALLOWED_HOSTS,
|
||||
),
|
||||
PlatformId.OSX_x64: RuntimeDependency(
|
||||
id="expert_darwin_amd64",
|
||||
platform_id="osx-x64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{EXPERT_VERSION}/expert_darwin_amd64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{expert_version}/expert_darwin_amd64",
|
||||
archive_type="binary",
|
||||
binary_name="expert_darwin_amd64",
|
||||
extract_path="expert",
|
||||
sha256="964f316f1633090b33aab392b6b85fb778c5fb3c0db862671424458da34b1d4d" if expert_version == EXPERT_VERSION else None,
|
||||
allowed_hosts=EXPERT_ALLOWED_HOSTS,
|
||||
),
|
||||
PlatformId.OSX_arm64: RuntimeDependency(
|
||||
id="expert_darwin_arm64",
|
||||
platform_id="osx-arm64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{EXPERT_VERSION}/expert_darwin_arm64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{expert_version}/expert_darwin_arm64",
|
||||
archive_type="binary",
|
||||
binary_name="expert_darwin_arm64",
|
||||
extract_path="expert",
|
||||
sha256="5fb5be151baedd635d99835cf3f9986afc9af6ae7b07bd001a1962f4298e45da" if expert_version == EXPERT_VERSION else None,
|
||||
allowed_hosts=EXPERT_ALLOWED_HOSTS,
|
||||
),
|
||||
PlatformId.WIN_x64: RuntimeDependency(
|
||||
id="expert_windows_amd64",
|
||||
platform_id="win-x64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{EXPERT_VERSION}/expert_windows_amd64.exe",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{expert_version}/expert_windows_amd64.exe",
|
||||
archive_type="binary",
|
||||
binary_name="expert_windows_amd64.exe",
|
||||
extract_path="expert.exe",
|
||||
),
|
||||
PlatformId.WIN_arm64: RuntimeDependency(
|
||||
id="expert_windows_arm64",
|
||||
platform_id="win-arm64",
|
||||
url=f"https://github.com/elixir-lang/expert/releases/download/{EXPERT_VERSION}/expert_windows_arm64.exe",
|
||||
archive_type="binary",
|
||||
binary_name="expert_windows_arm64.exe",
|
||||
extract_path="expert.exe",
|
||||
sha256="babee77d2653679021600b99c68d984d4463290cb221e0fc0d1093b3afdeb3b0" if expert_version == EXPERT_VERSION else None,
|
||||
allowed_hosts=EXPERT_ALLOWED_HOSTS,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -161,7 +173,12 @@ class ElixirTools(SolidLanguageServer):
|
||||
if not os.path.exists(executable_path):
|
||||
log.info(f"Downloading Expert binary from {dependency.url}")
|
||||
assert dependency.url is not None
|
||||
FileUtils.download_file(dependency.url, binary_path)
|
||||
FileUtils.download_file_verified(
|
||||
dependency.url,
|
||||
binary_path,
|
||||
expected_sha256=dependency.sha256,
|
||||
allowed_hosts=dependency.allowed_hosts,
|
||||
)
|
||||
|
||||
# Make the binary executable on Unix-like systems
|
||||
if not platform_id.value.startswith("win"):
|
||||
@@ -173,6 +190,10 @@ class ElixirTools(SolidLanguageServer):
|
||||
os.remove(executable_path)
|
||||
os.symlink(os.path.basename(binary_path), executable_path)
|
||||
|
||||
# normalizing the executable name on Windows
|
||||
if binary_path != executable_path and platform_id.value.startswith("win"):
|
||||
shutil.copy2(binary_path, executable_path)
|
||||
|
||||
assert os.path.exists(executable_path), f"Expert executable not found at {executable_path}"
|
||||
|
||||
log.info(f"Expert binary ready at: {executable_path}")
|
||||
|
||||
@@ -12,12 +12,12 @@ from overrides import override
|
||||
from sensai.util.logging import LogTime
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -76,13 +76,16 @@ class ElmLanguageServer(SolidLanguageServer):
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
elm_config = solidlsp_settings.get_ls_specific_settings(Language.ELM)
|
||||
elm_language_server_version = elm_config.get("elm_language_server_version", "2.8.0")
|
||||
npm_registry = elm_config.get("npm_registry")
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="elm-language-server",
|
||||
description="@elm-tooling/elm-language-server package",
|
||||
command=["npm", "install", "--prefix", "./", "@elm-tooling/elm-language-server@2.8.0"],
|
||||
command=build_npm_install_command("@elm-tooling/elm-language-server", elm_language_server_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -14,7 +14,7 @@ from overrides import override
|
||||
from serena.util.dotnet import DotNETUtil
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -22,11 +22,17 @@ from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
FSAUTOCOMPLETE_VERSION = "0.83.0"
|
||||
|
||||
|
||||
class FSharpLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides F# specific instantiation of the LanguageServer class using Ionide LSP (FsAutoComplete).
|
||||
Contains various configurations and settings specific to F# development.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["fsharp"]``:
|
||||
- fsautocomplete_version: Override the pinned FsAutoComplete version
|
||||
installed by Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
@@ -62,6 +68,8 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Setup runtime dependencies for F# Language Server and return the command to start the server.
|
||||
"""
|
||||
fsharp_settings = solidlsp_settings.get_ls_specific_settings(Language.FSHARP)
|
||||
fsautocomplete_version = fsharp_settings.get("fsautocomplete_version", FSAUTOCOMPLETE_VERSION)
|
||||
dotnet_exe = DotNETUtil("8.0", allow_higher_version=True).get_dotnet_path_or_raise()
|
||||
|
||||
RuntimeDependencyCollection(
|
||||
@@ -69,7 +77,7 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
RuntimeDependency(
|
||||
id="fsautocomplete",
|
||||
description="FsAutoComplete (Ionide F# Language Server)",
|
||||
command="dotnet tool install --tool-path ./ fsautocomplete",
|
||||
command=f"dotnet tool install --tool-path ./ fsautocomplete --version {fsautocomplete_version}",
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
@@ -94,7 +102,7 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
[dotnet_exe, "tool", "install", "--tool-path", fsharp_ls_dir, "fsautocomplete"],
|
||||
[dotnet_exe, "tool", "install", "--tool-path", fsharp_ls_dir, "fsautocomplete", "--version", fsautocomplete_version],
|
||||
cwd=fsharp_ls_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
|
||||
@@ -17,6 +17,15 @@ from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
VSCODE_JAVA_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
VSCODE_JAVA_SHA256_BY_PLATFORM = {
|
||||
"win-x64": "ef195b45bd260976ad2e84618f4044b5d7248deed41d647573f0ee22c4233df3",
|
||||
"linux-x64": "7660b7b527be6fda46a917966b34d828e7416d5cc84287b29b88e7b99c1737f9",
|
||||
"linux-arm64": "e15bc9b2a665d3453203402621b5441062aa41b0ec2d140661f439326fd248c1",
|
||||
"osx-x64": "03ae1db1a22c15561a620f1b722d6797d35d4faaa7c4666dbe6ca2715089852f",
|
||||
"osx-arm64": "bc00c2699d4b8d478eb9a1621db9d6d3a12ea0dcc247a9cd8040e8ac19c03933",
|
||||
}
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class GroovyRuntimeDependencyPaths:
|
||||
@@ -34,6 +43,13 @@ class GroovyLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Groovy specific instantiation of the LanguageServer class.
|
||||
Contains various configurations and settings specific to Groovy.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["groovy"]``:
|
||||
- ls_jar_path: Path to the Groovy Language Server JAR.
|
||||
- ls_java_home_path: Optional Java home to use instead of Serena's managed JRE.
|
||||
- ls_jar_options: Additional JVM/JAR options passed to the Groovy LS.
|
||||
- vscode_java_version: Override the pinned vscode-java runtime bundle version
|
||||
downloaded by Serena when it manages Java itself (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
@@ -76,6 +92,9 @@ class GroovyLanguageServer(SolidLanguageServer):
|
||||
Setup runtime dependencies for Groovy Language Server and return paths.
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY)
|
||||
vscode_java_version = groovy_settings.get("vscode_java_version", "1.42.0-561")
|
||||
vscode_java_tag = f"v{vscode_java_version.rsplit('-', 1)[0]}"
|
||||
|
||||
# Verify platform support
|
||||
assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), (
|
||||
@@ -105,39 +124,52 @@ class GroovyLanguageServer(SolidLanguageServer):
|
||||
runtime_dependencies = {
|
||||
"java": {
|
||||
"win-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-win32-x64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-win32-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["win-x64"] if vscode_java_version == "1.42.0-561" else None,
|
||||
},
|
||||
"linux-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-linux-x64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-linux-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-linux-x86_64/bin/java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["linux-x64"] if vscode_java_version == "1.42.0-561" else None,
|
||||
},
|
||||
"linux-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-linux-arm64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-linux-aarch64",
|
||||
"java_path": "extension/jre/21.0.7-linux-aarch64/bin/java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["linux-arm64"] if vscode_java_version == "1.42.0-561" else None,
|
||||
},
|
||||
"osx-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-x64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-macosx-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-macosx-x86_64/bin/java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-x64"] if vscode_java_version == "1.42.0-561" else None,
|
||||
},
|
||||
"osx-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
|
||||
"url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-arm64-{vscode_java_version}.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-macosx-aarch64",
|
||||
"java_path": "extension/jre/21.0.7-macosx-aarch64/bin/java",
|
||||
"sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-arm64"] if vscode_java_version == "1.42.0-561" else None,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
java_dependency = runtime_dependencies["java"][platform_id.value]
|
||||
java_home_relative_path = java_dependency["java_home_path"]
|
||||
java_relative_path = java_dependency["java_path"]
|
||||
java_download_url = java_dependency["url"]
|
||||
java_archive_type = java_dependency["archiveType"]
|
||||
assert java_home_relative_path is not None
|
||||
assert java_relative_path is not None
|
||||
assert java_download_url is not None
|
||||
assert java_archive_type is not None
|
||||
|
||||
static_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "groovy_language_server")
|
||||
os.makedirs(static_dir, exist_ok=True)
|
||||
@@ -145,12 +177,18 @@ class GroovyLanguageServer(SolidLanguageServer):
|
||||
java_dir = os.path.join(static_dir, "java")
|
||||
os.makedirs(java_dir, exist_ok=True)
|
||||
|
||||
java_home_path = os.path.join(java_dir, java_dependency["java_home_path"])
|
||||
java_path = os.path.join(java_dir, java_dependency["java_path"])
|
||||
java_home_path = os.path.join(java_dir, java_home_relative_path)
|
||||
java_path = os.path.join(java_dir, java_relative_path)
|
||||
|
||||
if not os.path.exists(java_path):
|
||||
log.info(f"Downloading Java for {platform_id.value}...")
|
||||
FileUtils.download_and_extract_archive(java_dependency["url"], java_dir, java_dependency["archiveType"])
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
java_download_url,
|
||||
java_dir,
|
||||
java_archive_type,
|
||||
expected_sha256=java_dependency["sha256"],
|
||||
allowed_hosts=VSCODE_JAVA_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
if not platform_id.value.startswith("win-"):
|
||||
os.chmod(java_path, 0o755)
|
||||
|
||||
@@ -24,12 +24,22 @@ log = logging.getLogger(__name__)
|
||||
# GitHub release version to download when not installed locally
|
||||
_DEFAULT_VERSION = "1.3.0"
|
||||
_GITHUB_RELEASE_BASE = "https://github.com/antaalt/shader-sense/releases/download"
|
||||
_HLSL_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
_HLSL_SHA256_BY_ASSET = {
|
||||
"shader-language-server-x86_64-pc-windows-msvc.zip": "a945b000c296cdeebb9ee2d4452cec2a0f26544dd076bb08bfdcade2278296a6",
|
||||
"shader-language-server-x86_64-unknown-linux-gnu.zip": "8c0a7b36f51cc58593762db3592ae13e21ca3cb982b2526cfaaf7c82e92ca089",
|
||||
"shader-language-server-aarch64-pc-windows-msvc.zip": "cdbd7b41e71cf6040d5cdb7e211ba4b76671a404ee0f7add281d72d3ab8dfa65",
|
||||
}
|
||||
|
||||
|
||||
class HlslLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Shader language server using shader-language-server.
|
||||
Supports .hlsl, .hlsli, .fx, .fxh, .cginc, .compute, .shader, .glsl, .vert, .frag, .geom, .tesc, .tese, .comp, .wgsl files.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["hlsl"]``:
|
||||
- version: Override the pinned shader-language-server version downloaded
|
||||
or built by Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
|
||||
@@ -53,7 +63,7 @@ class HlslLanguageServer(SolidLanguageServer):
|
||||
base_url = f"{_GITHUB_RELEASE_BASE}/{tag}"
|
||||
|
||||
# macOS has no pre-built binaries; build from source via cargo install
|
||||
cargo_install_cmd = f"cargo install shader_language_server --version {version} --root ."
|
||||
cargo_install_cmd = ["cargo", "install", "shader_language_server", "--version", version, "--root", "."]
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
@@ -64,6 +74,10 @@ class HlslLanguageServer(SolidLanguageServer):
|
||||
platform_id="win-x64",
|
||||
archive_type="zip",
|
||||
binary_name="shader-language-server.exe",
|
||||
sha256=_HLSL_SHA256_BY_ASSET["shader-language-server-x86_64-pc-windows-msvc.zip"]
|
||||
if version == _DEFAULT_VERSION
|
||||
else None,
|
||||
allowed_hosts=_HLSL_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="shader-language-server",
|
||||
@@ -72,6 +86,10 @@ class HlslLanguageServer(SolidLanguageServer):
|
||||
platform_id="linux-x64",
|
||||
archive_type="zip",
|
||||
binary_name="shader-language-server",
|
||||
sha256=_HLSL_SHA256_BY_ASSET["shader-language-server-x86_64-unknown-linux-gnu.zip"]
|
||||
if version == _DEFAULT_VERSION
|
||||
else None,
|
||||
allowed_hosts=_HLSL_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="shader-language-server",
|
||||
@@ -80,6 +98,10 @@ class HlslLanguageServer(SolidLanguageServer):
|
||||
platform_id="win-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="shader-language-server.exe",
|
||||
sha256=_HLSL_SHA256_BY_ASSET["shader-language-server-aarch64-pc-windows-msvc.zip"]
|
||||
if version == _DEFAULT_VERSION
|
||||
else None,
|
||||
allowed_hosts=_HLSL_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="shader-language-server",
|
||||
|
||||
@@ -17,7 +17,7 @@ from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from ..lsp_protocol_handler import lsp_types
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -59,6 +59,8 @@ class Intelephense(SolidLanguageServer):
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
intelephense_version = self._custom_settings.get("intelephense_version", "1.14.4")
|
||||
npm_registry = self._custom_settings.get("npm_registry")
|
||||
|
||||
# Install intelephense if not already installed
|
||||
intelephense_ls_dir = os.path.join(self._ls_resources_dir, "php-lsp")
|
||||
@@ -69,7 +71,7 @@ class Intelephense(SolidLanguageServer):
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="intelephense",
|
||||
command="npm install --prefix ./ intelephense@1.14.4",
|
||||
command=build_npm_install_command("intelephense", intelephense_version, npm_registry),
|
||||
platform_id="any",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -43,6 +43,14 @@ DEFAULT_KOTLIN_JVM_OPTIONS = "-Xmx2G"
|
||||
|
||||
# Default Kotlin Language Server version (can be overridden via ls_specific_settings)
|
||||
DEFAULT_KOTLIN_LSP_VERSION = "261.13587.0"
|
||||
KOTLIN_LSP_ALLOWED_HOSTS = ("download-cdn.jetbrains.com",)
|
||||
KOTLIN_LSP_SHA256_BY_SUFFIX = {
|
||||
"win-x64": "2806c2bd4810bd8e7ccc27d8c0ca4a5232a1c4f26ea1f4ba40e578b60860ccad",
|
||||
"linux-x64": "dc0ed2e70cb0d61fdabb26aefce8299b7a75c0dcfffb9413715e92caec6e83ec",
|
||||
"linux-aarch64": "d1dceb000fe06c5e2c30b95e7f4ab01d05101bd03ed448167feeb544a9f1d651",
|
||||
"mac-x64": "a3972f27229eba2c226060e54baea1c958c82c326dfc971bf53f72a74d0564a3",
|
||||
"mac-aarch64": "d4ea28b22b29cf906fe16d23698a8468f11646a6a66dcb15584f306aaefbee6c",
|
||||
}
|
||||
|
||||
# Platform-specific Kotlin LSP download suffixes
|
||||
PLATFORM_KOTLIN_SUFFIX = {
|
||||
@@ -111,8 +119,17 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
if not os.path.exists(kotlin_script):
|
||||
kotlin_lsp_version = self._custom_settings.get("kotlin_lsp_version", DEFAULT_KOTLIN_LSP_VERSION)
|
||||
kotlin_url = f"https://download-cdn.jetbrains.com/kotlin-lsp/{kotlin_lsp_version}/kotlin-lsp-{kotlin_lsp_version}-{kotlin_suffix}.zip"
|
||||
expected_sha256 = None
|
||||
if kotlin_lsp_version == DEFAULT_KOTLIN_LSP_VERSION:
|
||||
expected_sha256 = KOTLIN_LSP_SHA256_BY_SUFFIX[kotlin_suffix]
|
||||
log.info("Downloading Kotlin Language Server...")
|
||||
FileUtils.download_and_extract_archive(kotlin_url, static_dir, "zip")
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
kotlin_url,
|
||||
static_dir,
|
||||
"zip",
|
||||
expected_sha256=expected_sha256,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"):
|
||||
os.chmod(
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""
|
||||
Provides Lua specific instantiation of the LanguageServer class using lua-language-server.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["lua"]``:
|
||||
- lua_language_server_version: Override the pinned lua-language-server version
|
||||
downloaded by Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -7,21 +11,29 @@ import os
|
||||
import pathlib
|
||||
import platform
|
||||
import shutil
|
||||
import tarfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
LUA_LS_VERSION = "3.15.0"
|
||||
LUA_LS_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
LUA_LS_SHA256_BY_ASSET = {
|
||||
"lua-language-server-3.15.0-linux-x64.tar.gz": "4877b874c52fb7587707898da9026cc3a6c854d9bbab115ef49ac4e6a1b88007",
|
||||
"lua-language-server-3.15.0-linux-arm64.tar.gz": "7dff8edfed4f34cf6325ff384791287d95f9a8dd9615a5279c7c6af81cf8c45d",
|
||||
"lua-language-server-3.15.0-darwin-x64.tar.gz": "01d28a31e264434e51662814a68f584af068393caecfa158c4df5f7fdc3ca2f7",
|
||||
"lua-language-server-3.15.0-darwin-arm64.tar.gz": "050f5f493f65112afc116e31281a9f73918546782d3696485dc052724838f58b",
|
||||
"lua-language-server-3.15.0-win32-x64.zip": "76a10c05e8c947a448f00a61acead4240484cd1e2e8c66d54401c67d99b77535",
|
||||
}
|
||||
|
||||
|
||||
class LuaLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
@@ -37,7 +49,7 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
return super().is_ignored_dirname(dirname) or dirname in [".luarocks", "lua_modules", "node_modules", "build", "dist", ".cache"]
|
||||
|
||||
@staticmethod
|
||||
def _get_lua_ls_path() -> str | None:
|
||||
def _get_lua_ls_path(solidlsp_settings: SolidLSPSettings | None = None) -> str | None:
|
||||
"""Get the path to lua-language-server executable."""
|
||||
# First check if it's in PATH
|
||||
lua_ls = shutil.which("lua-language-server")
|
||||
@@ -48,17 +60,24 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
home = Path.home()
|
||||
possible_paths = [
|
||||
home / ".local" / "bin" / "lua-language-server",
|
||||
home / ".serena" / "language_servers" / "lua" / "bin" / "lua-language-server",
|
||||
Path("/usr/local/bin/lua-language-server"),
|
||||
Path("/opt/lua-language-server/bin/lua-language-server"),
|
||||
]
|
||||
|
||||
if solidlsp_settings is not None:
|
||||
ls_resource_dir = Path(LuaLanguageServer.ls_resources_dir(solidlsp_settings)) / "lua"
|
||||
possible_paths.extend(
|
||||
[
|
||||
ls_resource_dir / "bin" / "lua-language-server",
|
||||
ls_resource_dir / "bin" / "lua-language-server.exe",
|
||||
]
|
||||
)
|
||||
|
||||
# Add Windows-specific paths
|
||||
if platform.system() == "Windows":
|
||||
possible_paths.extend(
|
||||
[
|
||||
home / "AppData" / "Local" / "lua-language-server" / "bin" / "lua-language-server.exe",
|
||||
home / ".serena" / "language_servers" / "lua" / "bin" / "lua-language-server.exe",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -69,11 +88,12 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _download_lua_ls() -> str:
|
||||
def _download_lua_ls(solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""Download and install lua-language-server if not present."""
|
||||
lua_settings = solidlsp_settings.get_ls_specific_settings(Language.LUA)
|
||||
lua_ls_version = lua_settings.get("lua_language_server_version", LUA_LS_VERSION)
|
||||
system = platform.system()
|
||||
machine = platform.machine().lower()
|
||||
lua_ls_version = "3.15.0"
|
||||
|
||||
# Map platform and architecture to download URL
|
||||
if system == "Linux":
|
||||
@@ -100,31 +120,18 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
|
||||
download_url = f"https://github.com/LuaLS/lua-language-server/releases/download/{lua_ls_version}/{download_name}"
|
||||
|
||||
# Create installation directory
|
||||
install_dir = Path.home() / ".serena" / "language_servers" / "lua"
|
||||
install_dir = Path(LuaLanguageServer.ls_resources_dir(solidlsp_settings)) / "lua"
|
||||
install_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Download the file
|
||||
print(f"Downloading lua-language-server from {download_url}...")
|
||||
response = requests.get(download_url, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
# Save and extract
|
||||
download_path = install_dir / download_name
|
||||
with open(download_path, "wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
print(f"Extracting lua-language-server to {install_dir}...")
|
||||
if download_name.endswith(".tar.gz"):
|
||||
with tarfile.open(download_path, "r:gz") as tar:
|
||||
tar.extractall(install_dir)
|
||||
elif download_name.endswith(".zip"):
|
||||
with zipfile.ZipFile(download_path, "r") as zip_ref:
|
||||
zip_ref.extractall(install_dir)
|
||||
|
||||
# Clean up download file
|
||||
download_path.unlink()
|
||||
log.info("Downloading lua-language-server from %s", download_url)
|
||||
archive_type = "gztar" if download_name.endswith(".tar.gz") else "zip"
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
download_url,
|
||||
str(install_dir),
|
||||
archive_type,
|
||||
expected_sha256=LUA_LS_SHA256_BY_ASSET.get(download_name) if lua_ls_version == LUA_LS_VERSION else None,
|
||||
allowed_hosts=LUA_LS_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
# Make executable on Unix systems
|
||||
if system != "Windows":
|
||||
@@ -140,22 +147,22 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
raise RuntimeError("Failed to find lua-language-server executable after extraction")
|
||||
|
||||
@staticmethod
|
||||
def _setup_runtime_dependency() -> str:
|
||||
def _setup_runtime_dependency(solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""
|
||||
Check if required Lua runtime dependencies are available.
|
||||
Downloads lua-language-server if not present.
|
||||
"""
|
||||
lua_ls_path = LuaLanguageServer._get_lua_ls_path()
|
||||
lua_ls_path = LuaLanguageServer._get_lua_ls_path(solidlsp_settings)
|
||||
|
||||
if not lua_ls_path:
|
||||
print("lua-language-server not found. Downloading...")
|
||||
lua_ls_path = LuaLanguageServer._download_lua_ls()
|
||||
print(f"lua-language-server installed at: {lua_ls_path}")
|
||||
log.info("lua-language-server not found. Downloading...")
|
||||
lua_ls_path = LuaLanguageServer._download_lua_ls(solidlsp_settings)
|
||||
log.info("lua-language-server installed at: %s", lua_ls_path)
|
||||
|
||||
return lua_ls_path
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
lua_ls_path = self._setup_runtime_dependency()
|
||||
lua_ls_path = self._setup_runtime_dependency(solidlsp_settings)
|
||||
|
||||
super().__init__(
|
||||
config, repository_root_path, ProcessLaunchInfo(cmd=lua_ls_path, cwd=repository_root_path), "lua", solidlsp_settings
|
||||
|
||||
@@ -10,6 +10,8 @@ Requirements:
|
||||
or it will be automatically downloaded from GitHub releases.
|
||||
|
||||
Advanced settings via ls_specific_settings["luau"]:
|
||||
- luau_lsp_version: Override the pinned luau-lsp version downloaded by Serena
|
||||
(default: the bundled Serena version)
|
||||
- platform: "roblox" (default) or "standard"
|
||||
- roblox_security_level: "None", "PluginSecurity" (default),
|
||||
"LocalUserSecurity", or "RobloxScriptSecurity"
|
||||
@@ -23,14 +25,13 @@ import pathlib
|
||||
import platform
|
||||
import shutil
|
||||
import threading
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
@@ -38,6 +39,13 @@ log = logging.getLogger(__name__)
|
||||
|
||||
# Pin to a known stable release
|
||||
LUAU_LSP_VERSION = "1.63.0"
|
||||
LUAU_LSP_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
LUAU_LSP_SHA256_BY_ASSET = {
|
||||
"luau-lsp-linux-x86_64.zip": "e4b633ad9a2c15437f60f9e721263f79aa0da606867d8458f0e159a325bf2db8",
|
||||
"luau-lsp-linux-arm64.zip": "355be010f337a6772df6255c92e1fb28a59d194abe5c570453f4186472244355",
|
||||
"luau-lsp-macos.zip": "01c1d6dd5fee27295b2968915dabb08c192192c46d9fe9c97bf31a130c96b8cb",
|
||||
"luau-lsp-win64.zip": "eea596d47dc1c94a61ba1b78e6472bb4445bc3309780751515e6ab0a0abba57d",
|
||||
}
|
||||
|
||||
# Luau built-in docs CDN
|
||||
LUAU_DOCS_URL = "https://luau-lsp.pages.dev/api-docs/luau-en-us.json"
|
||||
@@ -51,6 +59,7 @@ SUPPORTED_ROBLOX_SECURITY_LEVELS = {
|
||||
"LocalUserSecurity",
|
||||
"RobloxScriptSecurity",
|
||||
}
|
||||
LUAU_DOCS_ALLOWED_HOSTS = ("luau-lsp.pages.dev",)
|
||||
|
||||
|
||||
class LuauLanguageServer(SolidLanguageServer):
|
||||
@@ -96,24 +105,18 @@ class LuauLanguageServer(SolidLanguageServer):
|
||||
if binary_path is not None:
|
||||
return binary_path
|
||||
|
||||
luau_lsp_version = self._custom_settings.get("luau_lsp_version", LUAU_LSP_VERSION)
|
||||
asset_name = self._get_luau_lsp_asset_name()
|
||||
download_url = f"https://github.com/JohnnyMorganz/luau-lsp/releases/download/{LUAU_LSP_VERSION}/{asset_name}"
|
||||
download_path = install_dir / asset_name
|
||||
download_url = f"https://github.com/JohnnyMorganz/luau-lsp/releases/download/{luau_lsp_version}/{asset_name}"
|
||||
|
||||
log.info("Downloading luau-lsp %s from %s", LUAU_LSP_VERSION, download_url)
|
||||
with requests.get(download_url, stream=True, timeout=60) as response:
|
||||
response.raise_for_status()
|
||||
with open(download_path, "wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
log.info("Extracting luau-lsp to %s", install_dir)
|
||||
with zipfile.ZipFile(download_path, "r") as zip_ref:
|
||||
zip_ref.extractall(install_dir)
|
||||
|
||||
if download_path.exists():
|
||||
download_path.unlink()
|
||||
log.info("Downloading luau-lsp %s from %s", luau_lsp_version, download_url)
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
download_url,
|
||||
str(install_dir),
|
||||
"zip",
|
||||
expected_sha256=LUAU_LSP_SHA256_BY_ASSET.get(asset_name) if luau_lsp_version == LUAU_LSP_VERSION else None,
|
||||
allowed_hosts=LUAU_LSP_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
binary_path = self._find_existing_binary(install_dir)
|
||||
if binary_path is None:
|
||||
@@ -164,9 +167,7 @@ class LuauLanguageServer(SolidLanguageServer):
|
||||
|
||||
try:
|
||||
log.info("Downloading %s from %s", description, url)
|
||||
response = requests.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
path.write_bytes(response.content)
|
||||
FileUtils.download_file_verified(url, str(path), allowed_hosts=LUAU_DOCS_ALLOWED_HOSTS)
|
||||
return str(path)
|
||||
except Exception as exc:
|
||||
log.warning("Failed to download %s: %s", description, exc)
|
||||
|
||||
@@ -26,57 +26,79 @@ from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
MARKSMAN_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
|
||||
|
||||
class Marksman(SolidLanguageServer):
|
||||
"""
|
||||
Provides Markdown specific instantiation of the LanguageServer class using marksman.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["markdown"]``:
|
||||
- marksman_version: Override the pinned Marksman release tag downloaded by
|
||||
Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
marksman_releases = "https://github.com/artempyanykh/marksman/releases/download/2024-12-18"
|
||||
runtime_dependencies = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-linux-x64",
|
||||
platform_id="linux-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-linux-arm64",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-macos",
|
||||
platform_id="osx-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-macos",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman.exe",
|
||||
platform_id="win-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman.exe",
|
||||
),
|
||||
]
|
||||
)
|
||||
DEFAULT_MARKSMAN_VERSION = "2024-12-18"
|
||||
|
||||
@classmethod
|
||||
def _runtime_dependencies(cls, version: str) -> RuntimeDependencyCollection:
|
||||
marksman_releases = f"https://github.com/artempyanykh/marksman/releases/download/{version}"
|
||||
default_version = version == cls.DEFAULT_MARKSMAN_VERSION
|
||||
return RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-linux-x64",
|
||||
platform_id="linux-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
sha256="b9cb666c643dfd9b699811fdfc445ed4c56be65c1d878c21d46847f0d7b0e475" if default_version else None,
|
||||
allowed_hosts=MARKSMAN_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-linux-arm64",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
sha256="b8d6972a56f3f9b7bbbf7c77ef8998e3b66fa82fb03c01398e224144486c9e73" if default_version else None,
|
||||
allowed_hosts=MARKSMAN_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-macos",
|
||||
platform_id="osx-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
sha256="7e18803966231a33ee107d0d26f69b41f2f0dc1332c52dd9729c2e29fb77be83" if default_version else None,
|
||||
allowed_hosts=MARKSMAN_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-macos",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
sha256="7e18803966231a33ee107d0d26f69b41f2f0dc1332c52dd9729c2e29fb77be83" if default_version else None,
|
||||
allowed_hosts=MARKSMAN_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman.exe",
|
||||
platform_id="win-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman.exe",
|
||||
sha256="39de9df039c8b0d627ac5918a9d8792ad20fc49e2461d1f5c906975c016799ec" if default_version else None,
|
||||
allowed_hosts=MARKSMAN_ALLOWED_HOSTS,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""Setup runtime dependencies for marksman and return the command to start the server."""
|
||||
deps = self.runtime_dependencies
|
||||
marksman_version = self._custom_settings.get("marksman_version", self.DEFAULT_MARKSMAN_VERSION)
|
||||
deps = self._runtime_dependencies(marksman_version)
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
|
||||
marksman_ls_dir = self._ls_resources_dir
|
||||
|
||||
@@ -37,13 +37,11 @@ import pathlib
|
||||
import platform
|
||||
import shutil
|
||||
import threading
|
||||
import zipfile
|
||||
from typing import Any, cast
|
||||
|
||||
import requests
|
||||
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LSPFileBuffer, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import DocumentSymbol, InitializeParams, SymbolInformation
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
@@ -52,10 +50,10 @@ log = logging.getLogger(__name__)
|
||||
# Environment variable for MATLAB installation path
|
||||
MATLAB_PATH_ENV_VAR = "MATLAB_PATH"
|
||||
|
||||
# VS Code Marketplace URL for MATLAB extension
|
||||
MATLAB_EXTENSION_URL = (
|
||||
"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/MathWorks/vsextensions/language-matlab/latest/vspackage"
|
||||
)
|
||||
MATLAB_EXTENSION_VERSION = "1.3.9"
|
||||
MATLAB_EXTENSION_URL = f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/MathWorks/vsextensions/language-matlab/{MATLAB_EXTENSION_VERSION}/vspackage"
|
||||
MATLAB_EXTENSION_SHA256 = "1da3add2c3a593fa0ebcdf1d15231faee8014de10f549c36915ab9d4f18390f2"
|
||||
MATLAB_EXTENSION_ALLOWED_HOSTS = ("marketplace.visualstudio.com",)
|
||||
|
||||
|
||||
class MatlabLanguageServer(SolidLanguageServer):
|
||||
@@ -72,6 +70,8 @@ class MatlabLanguageServer(SolidLanguageServer):
|
||||
|
||||
You can pass the following entries in ls_specific_settings["matlab"]:
|
||||
- matlab_path: Path to MATLAB installation (overrides MATLAB_PATH env var)
|
||||
- matlab_extension_version: Override the pinned MathWorks VS Code extension
|
||||
version downloaded by Serena (default: the bundled Serena version)
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
@@ -119,44 +119,14 @@ class MatlabLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
try:
|
||||
log.info(f"Downloading MATLAB extension from {url}")
|
||||
|
||||
# Create target directory for the extension
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
# Download with proper headers to mimic VS Code marketplace client
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
"Accept": "application/octet-stream, application/vsix, */*",
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers, stream=True, timeout=300)
|
||||
response.raise_for_status()
|
||||
|
||||
# Save to temporary VSIX file
|
||||
temp_file = os.path.join(target_dir, "matlab_extension_temp.vsix")
|
||||
total_size = int(response.headers.get("content-length", 0))
|
||||
|
||||
log.info(f"Downloading {total_size / 1024 / 1024:.1f} MB...")
|
||||
|
||||
with open(temp_file, "wb") as f:
|
||||
downloaded = 0
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
if total_size > 0 and downloaded % (10 * 1024 * 1024) == 0:
|
||||
progress = (downloaded / total_size) * 100
|
||||
log.info(f"Download progress: {progress:.1f}%")
|
||||
|
||||
log.info("Download complete, extracting...")
|
||||
|
||||
# Extract VSIX file (VSIX files are ZIP archives)
|
||||
with zipfile.ZipFile(temp_file, "r") as zip_ref:
|
||||
zip_ref.extractall(target_dir)
|
||||
|
||||
# Clean up temp file
|
||||
os.remove(temp_file)
|
||||
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
url,
|
||||
target_dir,
|
||||
"zip",
|
||||
expected_sha256=MATLAB_EXTENSION_SHA256 if url == MATLAB_EXTENSION_URL else None,
|
||||
allowed_hosts=MATLAB_EXTENSION_ALLOWED_HOSTS,
|
||||
)
|
||||
log.info("MATLAB extension extracted successfully")
|
||||
return True
|
||||
|
||||
@@ -213,10 +183,15 @@ class MatlabLanguageServer(SolidLanguageServer):
|
||||
|
||||
"""
|
||||
matlab_extension_dir = os.path.join(self._ls_resources_dir, "matlab-extension")
|
||||
matlab_extension_version = self._custom_settings.get("matlab_extension_version", MATLAB_EXTENSION_VERSION)
|
||||
matlab_extension_url = (
|
||||
"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/MathWorks/"
|
||||
f"vsextensions/language-matlab/{matlab_extension_version}/vspackage"
|
||||
)
|
||||
|
||||
log.info(f"Downloading MATLAB extension from: {MATLAB_EXTENSION_URL}")
|
||||
log.info(f"Downloading MATLAB extension from: {matlab_extension_url}")
|
||||
|
||||
if self._download_matlab_extension(MATLAB_EXTENSION_URL, matlab_extension_dir):
|
||||
if self._download_matlab_extension(matlab_extension_url, matlab_extension_dir):
|
||||
extension_path = os.path.join(matlab_extension_dir, "extension")
|
||||
if os.path.exists(extension_path):
|
||||
log.info("MATLAB extension downloaded and installed successfully")
|
||||
|
||||
@@ -12,7 +12,7 @@ from collections.abc import Iterable
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -21,6 +21,10 @@ from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
OMNISHARP_ALLOWED_HOSTS = ("roslynomnisharp.blob.core.windows.net", "download.visualstudio.microsoft.com")
|
||||
DEFAULT_OMNISHARP_VERSION = "1.39.10"
|
||||
DEFAULT_RAZOR_OMNISHARP_VERSION = "7.0.0-preview.23363.1"
|
||||
|
||||
|
||||
def breadth_first_file_scan(root: str) -> Iterable[str]:
|
||||
"""
|
||||
@@ -58,6 +62,10 @@ def find_least_depth_sln_file(root_dir: str) -> str | None:
|
||||
class OmniSharp(SolidLanguageServer):
|
||||
"""
|
||||
Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["csharp_omnisharp"]``:
|
||||
- omnisharp_version: Override the pinned OmniSharp version downloaded by Serena.
|
||||
- razor_omnisharp_version: Override the pinned Razor plugin version downloaded by Serena.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
@@ -150,6 +158,22 @@ class OmniSharp(SolidLanguageServer):
|
||||
with open(os.path.join(os.path.dirname(__file__), "omnisharp", "runtime_dependencies.json"), encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
omnisharp_settings = solidlsp_settings.get_ls_specific_settings(Language.CSHARP_OMNISHARP)
|
||||
omnisharp_version = omnisharp_settings.get("omnisharp_version", DEFAULT_OMNISHARP_VERSION)
|
||||
razor_omnisharp_version = omnisharp_settings.get("razor_omnisharp_version", DEFAULT_RAZOR_OMNISHARP_VERSION)
|
||||
for dependency in d["runtimeDependencies"]:
|
||||
if dependency["id"] == "OmniSharp":
|
||||
dependency["url"] = dependency["url"].replace(DEFAULT_OMNISHARP_VERSION, omnisharp_version)
|
||||
if "installPath" in dependency:
|
||||
dependency["installPath"] = dependency["installPath"].replace(DEFAULT_OMNISHARP_VERSION, omnisharp_version)
|
||||
if "installTestPath" in dependency:
|
||||
dependency["installTestPath"] = dependency["installTestPath"].replace(DEFAULT_OMNISHARP_VERSION, omnisharp_version)
|
||||
if omnisharp_version != DEFAULT_OMNISHARP_VERSION:
|
||||
dependency["integrity"] = None
|
||||
elif dependency["id"] == "RazorOmnisharp":
|
||||
dependency["url"] = dependency["url"].replace(DEFAULT_RAZOR_OMNISHARP_VERSION, razor_omnisharp_version)
|
||||
if razor_omnisharp_version != DEFAULT_RAZOR_OMNISHARP_VERSION:
|
||||
dependency["integrity"] = None
|
||||
|
||||
assert platform_id in [
|
||||
PlatformId.LINUX_x64,
|
||||
@@ -186,7 +210,13 @@ class OmniSharp(SolidLanguageServer):
|
||||
omnisharp_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "OmniSharp")
|
||||
if not os.path.exists(omnisharp_ls_dir):
|
||||
os.makedirs(omnisharp_ls_dir)
|
||||
FileUtils.download_and_extract_archive(runtime_dependencies["OmniSharp"]["url"], omnisharp_ls_dir, "zip")
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
runtime_dependencies["OmniSharp"]["url"],
|
||||
omnisharp_ls_dir,
|
||||
"zip",
|
||||
expected_sha256=runtime_dependencies["OmniSharp"].get("integrity"),
|
||||
allowed_hosts=OMNISHARP_ALLOWED_HOSTS,
|
||||
)
|
||||
omnisharp_executable_path = os.path.join(omnisharp_ls_dir, runtime_dependencies["OmniSharp"]["binaryName"])
|
||||
assert os.path.exists(omnisharp_executable_path)
|
||||
os.chmod(omnisharp_executable_path, 0o755)
|
||||
@@ -194,7 +224,13 @@ class OmniSharp(SolidLanguageServer):
|
||||
razor_omnisharp_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "RazorOmnisharp")
|
||||
if not os.path.exists(razor_omnisharp_ls_dir):
|
||||
os.makedirs(razor_omnisharp_ls_dir)
|
||||
FileUtils.download_and_extract_archive(runtime_dependencies["RazorOmnisharp"]["url"], razor_omnisharp_ls_dir, "zip")
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
runtime_dependencies["RazorOmnisharp"]["url"],
|
||||
razor_omnisharp_ls_dir,
|
||||
"zip",
|
||||
expected_sha256=runtime_dependencies["RazorOmnisharp"].get("integrity"),
|
||||
allowed_hosts=OMNISHARP_ALLOWED_HOSTS,
|
||||
)
|
||||
razor_omnisharp_dll_path = os.path.join(razor_omnisharp_ls_dir, runtime_dependencies["RazorOmnisharp"]["dll_path"])
|
||||
assert os.path.exists(razor_omnisharp_dll_path)
|
||||
|
||||
|
||||
@@ -310,7 +310,8 @@
|
||||
"x86_64"
|
||||
],
|
||||
"platformId": "win-x64",
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll"
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll",
|
||||
"integrity": "8D2DC5484018A2390A2366D05CADDD2BE21F619122DACD1BA1A87815B98D7361"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
@@ -324,7 +325,8 @@
|
||||
"x86"
|
||||
],
|
||||
"platformId": "win-x86",
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll"
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll",
|
||||
"integrity": "94C365B9D4B8D17612AF3574A3ABFB8CA75727866A8AF892EEEBE8F7559729A7"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
@@ -338,7 +340,8 @@
|
||||
"arm64"
|
||||
],
|
||||
"platformId": "win-arm64",
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll"
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll",
|
||||
"integrity": "2186E17AB20621C13A5B04F7053BDC744A9938AF6F9FA6259208F09F55999946"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
@@ -355,7 +358,8 @@
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "linux-x64",
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll"
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll",
|
||||
"integrity": "4B08C47D70AA96BE40AEDE22CDFCD909EB9073F1E1992E2B5B99D9D7A47F3276"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
@@ -371,7 +375,8 @@
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "linux-arm64"
|
||||
"platformId": "linux-arm64",
|
||||
"integrity": "A73FA7D0AC5E4DAC9FF15B9B675D90E30DFB45E40A66ECC70E22433EE028791C"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
@@ -387,7 +392,8 @@
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "linux-musl-x64"
|
||||
"platformId": "linux-musl-x64",
|
||||
"integrity": "7B0F8B1FEAC7726A5AC8D2C19F19B3530615F9226F75BB40D74EB0D8757CD11C"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
@@ -403,7 +409,8 @@
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "linux-musl-arm64"
|
||||
"platformId": "linux-musl-arm64",
|
||||
"integrity": "8BC53D60D53B7FC9E40144D862B0748BCB8768C7C18C8E6900D675DED14ABD08"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
@@ -419,7 +426,8 @@
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "osx-x64"
|
||||
"platformId": "osx-x64",
|
||||
"integrity": "7678022CBAC975A6D45E01A2985B88307E88F110578F3C04DB9D1C545BC69BDB"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
@@ -435,7 +443,8 @@
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "osx-arm64"
|
||||
"platformId": "osx-arm64",
|
||||
"integrity": "0A68EE21F5B8DDFDFC8D5E86E519C54D29B3A4D582F4D4284545C40A741AAFE6"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,24 @@ Contains various configurations and settings specific to Pascal and Free Pascal.
|
||||
|
||||
pasls installation strategy:
|
||||
1. Use existing pasls from PATH
|
||||
2. Download prebuilt binary from GitHub releases (auto-updated)
|
||||
2. Download a pinned prebuilt binary from GitHub releases
|
||||
|
||||
Supported platforms for binary download:
|
||||
- linux-x64, linux-arm64
|
||||
- osx-x64, osx-arm64
|
||||
- win-x64
|
||||
|
||||
Auto-update features:
|
||||
- Checks for updates every 24 hours via GitHub API
|
||||
Integrity features:
|
||||
- SHA256 checksum verification before installation
|
||||
- Atomic update with rollback on failure
|
||||
- Windows file locking detection
|
||||
|
||||
You can pass the following entries in ls_specific_settings["pascal"]:
|
||||
|
||||
Version management:
|
||||
- pasls_version: Override the pinned pasls version downloaded by Serena
|
||||
(default: the bundled Serena version).
|
||||
|
||||
Environment variables (recommended for CodeTools configuration):
|
||||
- pp: Path to FPC compiler driver, must be "fpc.exe" (e.g., "D:/laz32/fpc/bin/i386-win32/fpc.exe").
|
||||
Do NOT use backend compilers like ppc386.exe or ppcx64.exe - CodeTools queries fpc.exe for
|
||||
@@ -59,23 +62,27 @@ import zipfile
|
||||
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, quote_windows_path
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PASLS_VERSION = "v0.2.0"
|
||||
|
||||
|
||||
class PascalLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Pascal specific instantiation of the LanguageServer class using pasls.
|
||||
Contains various configurations and settings specific to Free Pascal and Lazarus.
|
||||
Supports overriding the bundled pasls version via ``pasls_version``.
|
||||
"""
|
||||
|
||||
# URL configuration
|
||||
PASLS_RELEASES_URL = "https://github.com/zen010101/pascal-language-server/releases/latest/download"
|
||||
PASLS_API_URL = "https://api.github.com/repos/zen010101/pascal-language-server/releases/latest"
|
||||
PASLS_VERSION = PASLS_VERSION
|
||||
PASLS_RELEASES_URL = f"https://github.com/zen010101/pascal-language-server/releases/download/{PASLS_VERSION}"
|
||||
PASLS_API_URL = f"https://api.github.com/repos/zen010101/pascal-language-server/releases/tags/{PASLS_VERSION}"
|
||||
|
||||
# Update check interval (seconds)
|
||||
UPDATE_CHECK_INTERVAL = 86400 # 24 hours
|
||||
@@ -523,21 +530,18 @@ class PascalLanguageServer(SolidLanguageServer):
|
||||
return False
|
||||
|
||||
# 3. Verify SHA256 checksum (critical security step, before extraction)
|
||||
if checksums:
|
||||
expected_sha256 = checksums.get(archive_filename)
|
||||
if expected_sha256:
|
||||
log.info(f"Verifying SHA256 checksum for {archive_filename}...")
|
||||
if not cls._verify_checksum(archive_path, expected_sha256):
|
||||
log.error(f"SHA256 checksum verification FAILED for {archive_filename}")
|
||||
log.error("Aborting installation due to checksum mismatch - possible security issue!")
|
||||
try:
|
||||
os.remove(archive_path)
|
||||
except OSError:
|
||||
pass
|
||||
return False
|
||||
log.info("SHA256 checksum verified successfully")
|
||||
else:
|
||||
log.warning(f"No checksum found for {archive_filename} in checksums file")
|
||||
expected_sha256 = checksums.get(archive_filename) if checksums else dep.sha256
|
||||
if expected_sha256:
|
||||
log.info(f"Verifying SHA256 checksum for {archive_filename}...")
|
||||
if not cls._verify_checksum(archive_path, expected_sha256):
|
||||
log.error(f"SHA256 checksum verification FAILED for {archive_filename}")
|
||||
log.error("Aborting installation due to checksum mismatch - possible security issue!")
|
||||
try:
|
||||
os.remove(archive_path)
|
||||
except OSError:
|
||||
pass
|
||||
return False
|
||||
log.info("SHA256 checksum verified successfully")
|
||||
else:
|
||||
log.warning("No checksums available - skipping verification (not recommended for production)")
|
||||
|
||||
@@ -607,12 +611,18 @@ class PascalLanguageServer(SolidLanguageServer):
|
||||
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Pascal Language Server (pasls).
|
||||
Automatically checks for updates every 24 hours with security verification.
|
||||
Downloads the pinned Serena-supported pasls release with checksum verification.
|
||||
|
||||
Returns:
|
||||
str: The command to start the pasls server
|
||||
|
||||
"""
|
||||
pascal_settings = solidlsp_settings.get_ls_specific_settings(Language.PASCAL)
|
||||
pasls_version = pascal_settings.get("pasls_version", PASLS_VERSION)
|
||||
cls.PASLS_VERSION = pasls_version
|
||||
cls.PASLS_RELEASES_URL = f"https://github.com/zen010101/pascal-language-server/releases/download/{pasls_version}"
|
||||
cls.PASLS_API_URL = f"https://api.github.com/repos/zen010101/pascal-language-server/releases/tags/{pasls_version}"
|
||||
|
||||
# Check if pasls is already in PATH
|
||||
pasls_in_path = shutil.which("pasls")
|
||||
if pasls_in_path:
|
||||
@@ -637,6 +647,7 @@ class PascalLanguageServer(SolidLanguageServer):
|
||||
platform_id="linux-x64",
|
||||
archive_type="gztar",
|
||||
binary_name="pasls",
|
||||
sha256="517259395b0a385a5e848cf48b967645a984be3dd456118bc08771283a822a5b",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="PascalLanguageServer",
|
||||
@@ -645,6 +656,7 @@ class PascalLanguageServer(SolidLanguageServer):
|
||||
platform_id="linux-arm64",
|
||||
archive_type="gztar",
|
||||
binary_name="pasls",
|
||||
sha256="cb4986941cfdcf9cb74ece6bbb53a443390a908e880108a37f4ccf82b2d6c502",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="PascalLanguageServer",
|
||||
@@ -653,6 +665,7 @@ class PascalLanguageServer(SolidLanguageServer):
|
||||
platform_id="osx-x64",
|
||||
archive_type="zip",
|
||||
binary_name="pasls",
|
||||
sha256="0abfcd98f63f77dba74094339a40d4407b69317c1c77b13a26b9b7dbdfd885f1",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="PascalLanguageServer",
|
||||
@@ -661,6 +674,7 @@ class PascalLanguageServer(SolidLanguageServer):
|
||||
platform_id="osx-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="pasls",
|
||||
sha256="d4c2411e406af96ceae12b11e77fdb0c684ca15a68bfd8b4f9c6fe1fbdf515a7",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="PascalLanguageServer",
|
||||
@@ -669,6 +683,7 @@ class PascalLanguageServer(SolidLanguageServer):
|
||||
platform_id="win-x64",
|
||||
archive_type="zip",
|
||||
binary_name="pasls.exe",
|
||||
sha256="1493c31552e6f90a59800b2d44669e01fc4551d3647f3cea5dd105a9f6bc73e5",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -22,6 +22,8 @@ log = logging.getLogger(__name__)
|
||||
|
||||
PHPACTOR_VERSION = "2025.12.21.1"
|
||||
PHPACTOR_PHAR_URL = f"https://github.com/phpactor/phpactor/releases/download/{PHPACTOR_VERSION}/phpactor.phar"
|
||||
PHPACTOR_PHAR_SHA256 = "53bbe9625cd9b5e9b394bc2f595fbad13dbbe6dfc96950c56dea3b5d9a246cc3"
|
||||
PHPACTOR_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
|
||||
|
||||
class PhpactorServer(SolidLanguageServer):
|
||||
@@ -33,6 +35,8 @@ class PhpactorServer(SolidLanguageServer):
|
||||
|
||||
You can pass the following entries in ls_specific_settings["php_phpactor"]:
|
||||
- ignore_vendor: whether to ignore directories named "vendor" (default: true)
|
||||
- phpactor_version: Override the pinned Phpactor PHAR version downloaded by
|
||||
Serena (default: the bundled Serena version)
|
||||
"""
|
||||
|
||||
@override
|
||||
@@ -44,6 +48,8 @@ class PhpactorServer(SolidLanguageServer):
|
||||
"""
|
||||
Setup runtime dependencies for Phpactor and return the path to the PHAR file.
|
||||
"""
|
||||
phpactor_version = self._custom_settings.get("phpactor_version", PHPACTOR_VERSION)
|
||||
phpactor_phar_url = f"https://github.com/phpactor/phpactor/releases/download/{phpactor_version}/phpactor.phar"
|
||||
# Verify PHP is installed
|
||||
php_path = shutil.which("php")
|
||||
assert php_path is not None, (
|
||||
@@ -65,8 +71,14 @@ class PhpactorServer(SolidLanguageServer):
|
||||
phpactor_phar_path = os.path.join(self._ls_resources_dir, "phpactor.phar")
|
||||
if not os.path.exists(phpactor_phar_path):
|
||||
os.makedirs(self._ls_resources_dir, exist_ok=True)
|
||||
log.info(f"Downloading phpactor PHAR from {PHPACTOR_PHAR_URL}")
|
||||
FileUtils.download_and_extract_archive(PHPACTOR_PHAR_URL, phpactor_phar_path, "binary")
|
||||
log.info(f"Downloading phpactor PHAR from {phpactor_phar_url}")
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
phpactor_phar_url,
|
||||
phpactor_phar_path,
|
||||
"binary",
|
||||
expected_sha256=PHPACTOR_PHAR_SHA256 if phpactor_version == PHPACTOR_VERSION else None,
|
||||
allowed_hosts=PHPACTOR_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
assert os.path.exists(phpactor_phar_path), f"phpactor PHAR not found at {phpactor_phar_path}, download may have failed."
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""
|
||||
Provides PowerShell specific instantiation of the LanguageServer class using PowerShell Editor Services.
|
||||
Contains various configurations and settings specific to PowerShell scripting.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["powershell"]``:
|
||||
- pses_version: Override the pinned PowerShell Editor Services version
|
||||
downloaded by Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -10,14 +14,13 @@ import platform
|
||||
import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -26,6 +29,8 @@ log = logging.getLogger(__name__)
|
||||
|
||||
# PowerShell Editor Services version to download
|
||||
PSES_VERSION = "4.4.0"
|
||||
PSES_SHA256 = "690b91092989a0f66e6f43986166aaef69d64b559a9fda51feed882e1103fbcc"
|
||||
PSES_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
|
||||
|
||||
class PowerShellLanguageServer(SolidLanguageServer):
|
||||
@@ -98,31 +103,24 @@ class PowerShellLanguageServer(SolidLanguageServer):
|
||||
@classmethod
|
||||
def _download_pses(cls, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""Download and install PowerShell Editor Services."""
|
||||
ps_settings = solidlsp_settings.get_ls_specific_settings(Language.POWERSHELL)
|
||||
pses_version = ps_settings.get("pses_version", PSES_VERSION)
|
||||
download_url = (
|
||||
f"https://github.com/PowerShell/PowerShellEditorServices/releases/download/v{PSES_VERSION}/PowerShellEditorServices.zip"
|
||||
f"https://github.com/PowerShell/PowerShellEditorServices/releases/download/v{pses_version}/PowerShellEditorServices.zip"
|
||||
)
|
||||
|
||||
# Create installation directory
|
||||
install_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "powershell"
|
||||
install_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Download the file
|
||||
log.info(f"Downloading PowerShell Editor Services from {download_url}...")
|
||||
response = requests.get(download_url, stream=True, timeout=120)
|
||||
response.raise_for_status()
|
||||
|
||||
# Save the zip file
|
||||
zip_path = install_dir / "PowerShellEditorServices.zip"
|
||||
with open(zip_path, "wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
log.info(f"Extracting PowerShell Editor Services to {install_dir}...")
|
||||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||
zip_ref.extractall(install_dir)
|
||||
|
||||
# Clean up zip file
|
||||
zip_path.unlink()
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
download_url,
|
||||
str(install_dir),
|
||||
"zip",
|
||||
expected_sha256=PSES_SHA256 if pses_version == PSES_VERSION else None,
|
||||
allowed_hosts=PSES_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
start_script = install_dir / "PowerShellEditorServices" / "Start-EditorServices.ps1"
|
||||
if not start_script.exists():
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
"""
|
||||
Ruby LSP Language Server implementation using Shopify's ruby-lsp.
|
||||
Provides modern Ruby language server capabilities with improved performance.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["ruby"]``:
|
||||
- ruby_lsp_version: Override the pinned ruby-lsp gem version installed by
|
||||
Serena when no project-local or global ruby-lsp is already available
|
||||
(default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -14,18 +19,21 @@ import threading
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, InitializeResult
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
RUBY_LSP_VERSION = "0.26.8"
|
||||
|
||||
|
||||
class RubyLsp(SolidLanguageServer):
|
||||
"""
|
||||
Provides Ruby specific instantiation of the LanguageServer class using ruby-lsp.
|
||||
Contains various configurations and settings specific to Ruby with modern LSP features.
|
||||
Supports overriding the bundled gem version via ``ruby_lsp_version``.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
@@ -33,7 +41,7 @@ class RubyLsp(SolidLanguageServer):
|
||||
Creates a RubyLsp instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
ruby_lsp_executable = self._setup_runtime_dependencies(config, repository_root_path)
|
||||
ruby_lsp_executable = self._setup_runtime_dependencies(config, repository_root_path, solidlsp_settings)
|
||||
super().__init__(
|
||||
config, repository_root_path, ProcessLaunchInfo(cmd=ruby_lsp_executable, cwd=repository_root_path), "ruby", solidlsp_settings
|
||||
)
|
||||
@@ -92,11 +100,15 @@ class RubyLsp(SolidLanguageServer):
|
||||
return shutil.which(executable_name)
|
||||
|
||||
@staticmethod
|
||||
def _setup_runtime_dependencies(config: LanguageServerConfig, repository_root_path: str) -> list[str]:
|
||||
def _setup_runtime_dependencies(
|
||||
config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
) -> list[str]:
|
||||
"""
|
||||
Setup runtime dependencies for ruby-lsp and return the command list to start the server.
|
||||
Installation strategy: Bundler project > global ruby-lsp > gem install ruby-lsp
|
||||
Installation strategy: Bundler project > global ruby-lsp > gem install ruby-lsp at the pinned version
|
||||
"""
|
||||
ls_specific_settings = solidlsp_settings.get_ls_specific_settings(Language.RUBY)
|
||||
ruby_lsp_version = ls_specific_settings.get("ruby_lsp_version", RUBY_LSP_VERSION)
|
||||
# Detect rbenv-managed Ruby environment
|
||||
# When .ruby-version exists, it indicates the project uses rbenv for version management.
|
||||
# rbenv automatically reads .ruby-version to determine which Ruby version to use.
|
||||
@@ -215,7 +227,12 @@ class RubyLsp(SolidLanguageServer):
|
||||
# Try to install ruby-lsp globally
|
||||
log.info("ruby-lsp not found, attempting to install globally...")
|
||||
try:
|
||||
subprocess.run(["gem", "install", "ruby-lsp"], check=True, capture_output=True, cwd=repository_root_path)
|
||||
subprocess.run(
|
||||
["gem", "install", "ruby-lsp", "-v", ruby_lsp_version],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
cwd=repository_root_path,
|
||||
)
|
||||
log.info("Successfully installed ruby-lsp globally")
|
||||
# Find the newly installed ruby-lsp executable
|
||||
ruby_lsp_path = RubyLsp._find_executable_with_extensions("ruby-lsp")
|
||||
@@ -226,9 +243,11 @@ class RubyLsp(SolidLanguageServer):
|
||||
raise RuntimeError(
|
||||
f"Failed to install ruby-lsp globally: {error_msg}\n"
|
||||
"For Bundler projects, please add 'gem \"ruby-lsp\"' to your Gemfile and run 'bundle install'.\n"
|
||||
"Alternatively, install globally: gem install ruby-lsp"
|
||||
f"Alternatively, install globally: gem install ruby-lsp -v {ruby_lsp_version}"
|
||||
) from e
|
||||
raise RuntimeError(f"Failed to install ruby-lsp: {error_msg}\nPlease try installing manually: gem install ruby-lsp") from e
|
||||
raise RuntimeError(
|
||||
f"Failed to install ruby-lsp: {error_msg}\nPlease try installing manually: gem install ruby-lsp -v {ruby_lsp_version}"
|
||||
) from e
|
||||
|
||||
@staticmethod
|
||||
def _detect_rails_project(repository_root_path: str) -> bool:
|
||||
|
||||
@@ -12,7 +12,7 @@ import threading
|
||||
from time import sleep
|
||||
from typing import Any
|
||||
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -69,13 +69,19 @@ class SolidityLanguageServer(SolidLanguageServer):
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install Node.js and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
solidity_language_server_version = self._custom_settings.get("solidity_language_server_version", "0.8.4")
|
||||
npm_registry = self._custom_settings.get("npm_registry")
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="solidity-language-server",
|
||||
description="Nomic Foundation Solidity Language Server",
|
||||
command="npm install --prefix ./ @nomicfoundation/solidity-language-server@0.8.4",
|
||||
command=build_npm_install_command(
|
||||
"@nomicfoundation/solidity-language-server",
|
||||
solidity_language_server_version,
|
||||
npm_registry,
|
||||
),
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -18,11 +18,17 @@ from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
VERIBLE_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
|
||||
|
||||
class SystemVerilogLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
SystemVerilog language server using verible-verilog-ls.
|
||||
Supports .sv, .svh, .v, .vh files.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["systemverilog"]``:
|
||||
- verible_version: Override the pinned Verible release version downloaded
|
||||
by Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
|
||||
@@ -67,6 +73,8 @@ class SystemVerilogLanguageServer(SolidLanguageServer):
|
||||
platform_id="linux-x64",
|
||||
archive_type="gztar",
|
||||
binary_name=f"verible-{verible_version}/bin/verible-verilog-ls",
|
||||
sha256="f52e5920ef63f70620a6086e09dea8bd778147cd7a9ff827bb7de5d6316b1754",
|
||||
allowed_hosts=VERIBLE_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="verible-ls",
|
||||
@@ -75,6 +83,8 @@ class SystemVerilogLanguageServer(SolidLanguageServer):
|
||||
platform_id="linux-arm64",
|
||||
archive_type="gztar",
|
||||
binary_name=f"verible-{verible_version}/bin/verible-verilog-ls",
|
||||
sha256="30dd9c6f6e0f4840d6ba0c9e81ea2774a50b5a1a523a855245f9a9b4beb6b58b",
|
||||
allowed_hosts=VERIBLE_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="verible-ls",
|
||||
@@ -83,6 +93,8 @@ class SystemVerilogLanguageServer(SolidLanguageServer):
|
||||
platform_id="osx-x64",
|
||||
archive_type="gztar",
|
||||
binary_name=f"verible-{verible_version}/bin/verible-verilog-ls",
|
||||
sha256="9ef92e9ad345285dd593763e10ca61c8532fcf47bbb6cf4448f9a9423882d662",
|
||||
allowed_hosts=VERIBLE_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="verible-ls",
|
||||
@@ -91,6 +103,8 @@ class SystemVerilogLanguageServer(SolidLanguageServer):
|
||||
platform_id="osx-arm64",
|
||||
archive_type="gztar",
|
||||
binary_name=f"verible-{verible_version}/bin/verible-verilog-ls",
|
||||
sha256="9ef92e9ad345285dd593763e10ca61c8532fcf47bbb6cf4448f9a9423882d662",
|
||||
allowed_hosts=VERIBLE_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="verible-ls",
|
||||
@@ -99,6 +113,8 @@ class SystemVerilogLanguageServer(SolidLanguageServer):
|
||||
platform_id="win-x64",
|
||||
archive_type="zip",
|
||||
binary_name=f"verible-{verible_version}/bin/verible-verilog-ls.exe",
|
||||
sha256="729aa244036da4a4f87bc026d33555456fc7f7be79778d983ebe9c893f4a0ca3",
|
||||
allowed_hosts=VERIBLE_ALLOWED_HOSTS,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
"""
|
||||
Provides TOML specific instantiation of the LanguageServer class using Taplo.
|
||||
Contains various configurations and settings specific to TOML files.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["toml"]``:
|
||||
- taplo_version: Override the pinned Taplo version downloaded by Serena
|
||||
(default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
import gzip
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import socket
|
||||
import stat
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
# Download timeout in seconds (prevents indefinite hangs)
|
||||
DOWNLOAD_TIMEOUT_SECONDS = 120
|
||||
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_utils import PathUtils
|
||||
from solidlsp.ls_utils import FileUtils, PathUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
@@ -28,6 +25,7 @@ log = logging.getLogger(__name__)
|
||||
# Taplo release version and download URLs
|
||||
TAPLO_VERSION = "0.10.0"
|
||||
TAPLO_DOWNLOAD_BASE = f"https://github.com/tamasfe/taplo/releases/download/{TAPLO_VERSION}"
|
||||
TAPLO_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")
|
||||
|
||||
# SHA256 checksums for Taplo releases (verified from official GitHub releases)
|
||||
# Source: https://github.com/tamasfe/taplo/releases/tag/0.10.0
|
||||
@@ -43,17 +41,7 @@ TAPLO_SHA256_CHECKSUMS: dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
def _verify_sha256(file_path: str, expected_hash: str) -> bool:
|
||||
"""Verify SHA256 checksum of a downloaded file."""
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(file_path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(8192), b""):
|
||||
sha256_hash.update(chunk)
|
||||
actual_hash = sha256_hash.hexdigest()
|
||||
return actual_hash.lower() == expected_hash.lower()
|
||||
|
||||
|
||||
def _get_taplo_download_url() -> tuple[str, str]:
|
||||
def _get_taplo_download_url(version: str = TAPLO_VERSION) -> tuple[str, str]:
|
||||
"""
|
||||
Get the appropriate Taplo download URL for the current platform.
|
||||
|
||||
@@ -88,7 +76,7 @@ def _get_taplo_download_url() -> tuple[str, str]:
|
||||
filename = f"taplo-linux-{arch}.gz"
|
||||
executable = "taplo"
|
||||
|
||||
return f"{TAPLO_DOWNLOAD_BASE}/{filename}", executable
|
||||
return f"https://github.com/tamasfe/taplo/releases/download/{version}/{filename}", executable
|
||||
|
||||
|
||||
class TaploServer(SolidLanguageServer):
|
||||
@@ -144,7 +132,8 @@ class TaploServer(SolidLanguageServer):
|
||||
taplo_dir = os.path.join(self._ls_resources_dir, "taplo")
|
||||
os.makedirs(taplo_dir, exist_ok=True)
|
||||
|
||||
_, executable_name = _get_taplo_download_url()
|
||||
taplo_version = self._custom_settings.get("taplo_version", TAPLO_VERSION)
|
||||
_, executable_name = _get_taplo_download_url(taplo_version)
|
||||
taplo_executable = os.path.join(taplo_dir, executable_name)
|
||||
|
||||
if os.path.exists(taplo_executable) and os.access(taplo_executable, os.X_OK):
|
||||
@@ -152,8 +141,8 @@ class TaploServer(SolidLanguageServer):
|
||||
return taplo_executable
|
||||
|
||||
# Download and install Taplo
|
||||
log.info(f"Taplo not found. Downloading version {TAPLO_VERSION}...")
|
||||
self._download_taplo(taplo_dir, taplo_executable)
|
||||
log.info(f"Taplo not found. Downloading version {taplo_version}...")
|
||||
self._download_taplo(taplo_dir, taplo_executable, taplo_version)
|
||||
|
||||
if not os.path.exists(taplo_executable):
|
||||
raise FileNotFoundError(
|
||||
@@ -167,64 +156,30 @@ class TaploServer(SolidLanguageServer):
|
||||
return [core_path, "lsp", "stdio"]
|
||||
|
||||
@classmethod
|
||||
def _download_taplo(cls, install_dir: str, executable_path: str) -> None:
|
||||
"""Download and extract Taplo binary with SHA256 verification."""
|
||||
# TODO: consider using existing download utilities in SolidLSP instead of the custom logic here
|
||||
download_url, _ = _get_taplo_download_url()
|
||||
def _download_taplo(cls, install_dir: str, executable_path: str, version: str = TAPLO_VERSION) -> None:
|
||||
"""Download and extract Taplo binary using the shared verified download helper."""
|
||||
download_url, _ = _get_taplo_download_url(version)
|
||||
archive_filename = os.path.basename(download_url)
|
||||
expected_hash = TAPLO_SHA256_CHECKSUMS.get(archive_filename) if version == TAPLO_VERSION else None
|
||||
if expected_hash is None and version == TAPLO_VERSION:
|
||||
raise RuntimeError(f"No SHA256 checksum configured for Taplo archive: {archive_filename}")
|
||||
|
||||
try:
|
||||
log.info(f"Downloading Taplo from: {download_url}")
|
||||
archive_path = os.path.join(install_dir, archive_filename)
|
||||
|
||||
# Download the archive with timeout to prevent indefinite hangs
|
||||
old_timeout = socket.getdefaulttimeout()
|
||||
try:
|
||||
socket.setdefaulttimeout(DOWNLOAD_TIMEOUT_SECONDS)
|
||||
urllib.request.urlretrieve(download_url, archive_path)
|
||||
finally:
|
||||
socket.setdefaulttimeout(old_timeout)
|
||||
|
||||
# Verify SHA256 checksum
|
||||
expected_hash = TAPLO_SHA256_CHECKSUMS.get(archive_filename)
|
||||
if expected_hash:
|
||||
if not _verify_sha256(archive_path, expected_hash):
|
||||
os.remove(archive_path)
|
||||
raise RuntimeError(
|
||||
f"SHA256 checksum verification failed for {archive_filename}. "
|
||||
"The downloaded file may be corrupted or tampered with. "
|
||||
"Try installing manually: cargo install taplo-cli --locked"
|
||||
)
|
||||
log.info(f"SHA256 checksum verified for {archive_filename}")
|
||||
else:
|
||||
log.warning(
|
||||
f"No SHA256 checksum available for {archive_filename}. "
|
||||
"Skipping verification - consider installing manually: cargo install taplo-cli --locked"
|
||||
)
|
||||
|
||||
# Extract based on format
|
||||
if archive_path.endswith(".gz") and not archive_path.endswith(".tar.gz"):
|
||||
# Single file gzip
|
||||
with gzip.open(archive_path, "rb") as f_in:
|
||||
with open(executable_path, "wb") as f_out:
|
||||
f_out.write(f_in.read())
|
||||
elif archive_path.endswith(".zip"):
|
||||
import zipfile
|
||||
|
||||
with zipfile.ZipFile(archive_path, "r") as zip_ref:
|
||||
# Security: Validate paths to prevent zip slip vulnerability
|
||||
for member in zip_ref.namelist():
|
||||
member_path = os.path.normpath(os.path.join(install_dir, member))
|
||||
if not member_path.startswith(os.path.normpath(install_dir)):
|
||||
raise RuntimeError(f"Zip slip detected: {member} attempts to escape install directory")
|
||||
zip_ref.extractall(install_dir)
|
||||
archive_type = "zip" if archive_filename.endswith(".zip") else "gz"
|
||||
target_path = install_dir if archive_type == "zip" else executable_path
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
download_url,
|
||||
target_path,
|
||||
archive_type,
|
||||
expected_sha256=expected_hash,
|
||||
allowed_hosts=TAPLO_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
# Make executable on Unix systems
|
||||
if os.name != "nt":
|
||||
os.chmod(executable_path, os.stat(executable_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
# Clean up archive
|
||||
os.remove(archive_path)
|
||||
log.info(f"Taplo installed successfully at: {executable_path}")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import cast
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import PathUtils, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -16,10 +16,16 @@ from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TERRAFORM_LS_ALLOWED_HOSTS = ("releases.hashicorp.com",)
|
||||
|
||||
|
||||
class TerraformLS(SolidLanguageServer):
|
||||
"""
|
||||
Provides Terraform specific instantiation of the LanguageServer class using terraform-ls.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["terraform"]``:
|
||||
- terraform_ls_version: Override the pinned terraform-ls version downloaded
|
||||
by Serena (default: the bundled Serena version).
|
||||
"""
|
||||
|
||||
@override
|
||||
@@ -92,48 +98,60 @@ class TerraformLS(SolidLanguageServer):
|
||||
Downloads and installs terraform-ls if not already present.
|
||||
"""
|
||||
cls._ensure_tf_command_available()
|
||||
terraform_settings = solidlsp_settings.get_ls_specific_settings(Language.TERRAFORM)
|
||||
terraform_ls_version = terraform_settings.get("terraform_ls_version", "0.36.5")
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="TerraformLS",
|
||||
description="terraform-ls for macOS (ARM64)",
|
||||
url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_darwin_arm64.zip",
|
||||
url=f"https://releases.hashicorp.com/terraform-ls/{terraform_ls_version}/terraform-ls_{terraform_ls_version}_darwin_arm64.zip",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="terraform-ls",
|
||||
sha256="fee8743aa71fe2d8b0b9b91283b844cfa57d58457306a62e53a8f38d143cec8c" if terraform_ls_version == "0.36.5" else None,
|
||||
allowed_hosts=TERRAFORM_LS_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="TerraformLS",
|
||||
description="terraform-ls for macOS (x64)",
|
||||
url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_darwin_amd64.zip",
|
||||
url=f"https://releases.hashicorp.com/terraform-ls/{terraform_ls_version}/terraform-ls_{terraform_ls_version}_darwin_amd64.zip",
|
||||
platform_id="osx-x64",
|
||||
archive_type="zip",
|
||||
binary_name="terraform-ls",
|
||||
sha256="17c5c480f8eec7e528292565f1c05d5097a41edf7ef8ee2a9f3a18d288a1415a" if terraform_ls_version == "0.36.5" else None,
|
||||
allowed_hosts=TERRAFORM_LS_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="TerraformLS",
|
||||
description="terraform-ls for Linux (ARM64)",
|
||||
url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_linux_arm64.zip",
|
||||
url=f"https://releases.hashicorp.com/terraform-ls/{terraform_ls_version}/terraform-ls_{terraform_ls_version}_linux_arm64.zip",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="zip",
|
||||
binary_name="terraform-ls",
|
||||
sha256="724f45029f32d02d88b1952c7d1526c59fc8cd5dae49e31b9fed676a83f6cae7" if terraform_ls_version == "0.36.5" else None,
|
||||
allowed_hosts=TERRAFORM_LS_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="TerraformLS",
|
||||
description="terraform-ls for Linux (x64)",
|
||||
url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_linux_amd64.zip",
|
||||
url=f"https://releases.hashicorp.com/terraform-ls/{terraform_ls_version}/terraform-ls_{terraform_ls_version}_linux_amd64.zip",
|
||||
platform_id="linux-x64",
|
||||
archive_type="zip",
|
||||
binary_name="terraform-ls",
|
||||
sha256="37e645cc54fd03e863157e2a3e773e7a5ff1d6cb3d045e4c20860cac1f550a44" if terraform_ls_version == "0.36.5" else None,
|
||||
allowed_hosts=TERRAFORM_LS_ALLOWED_HOSTS,
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="TerraformLS",
|
||||
description="terraform-ls for Windows (x64)",
|
||||
url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_windows_amd64.zip",
|
||||
url=f"https://releases.hashicorp.com/terraform-ls/{terraform_ls_version}/terraform-ls_{terraform_ls_version}_windows_amd64.zip",
|
||||
platform_id="win-x64",
|
||||
archive_type="zip",
|
||||
binary_name="terraform-ls.exe",
|
||||
sha256="a9223462cac9e1c0e6ba33043fbf9fb4483609b6970b5681a6306b04366698ec" if terraform_ls_version == "0.36.5" else None,
|
||||
allowed_hosts=TERRAFORM_LS_ALLOWED_HOSTS,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ from solidlsp.ls_utils import PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -149,19 +149,20 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
language_specific_config = self._custom_settings
|
||||
typescript_version = language_specific_config.get("typescript_version", "5.9.3")
|
||||
typescript_language_server_version = language_specific_config.get("typescript_language_server_version", "5.1.3")
|
||||
npm_registry = language_specific_config.get("npm_registry")
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="typescript",
|
||||
description="typescript package",
|
||||
command=["npm", "install", "--prefix", "./", f"typescript@{typescript_version}"],
|
||||
command=build_npm_install_command("typescript", typescript_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="typescript-language-server",
|
||||
description="typescript-language-server package",
|
||||
command=["npm", "install", "--prefix", "./", f"typescript-language-server@{typescript_language_server_version}"],
|
||||
command=build_npm_install_command("typescript-language-server", typescript_language_server_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -14,13 +14,13 @@ from typing import cast
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -72,13 +72,16 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
PlatformId.WIN_arm64,
|
||||
]
|
||||
assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for vtsls at the moment"
|
||||
vts_config = solidlsp_settings.get_ls_specific_settings(Language.TYPESCRIPT_VTS)
|
||||
vtsls_version = vts_config.get("vtsls_version", "0.2.9")
|
||||
npm_registry = vts_config.get("npm_registry")
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="vtsls",
|
||||
description="vtsls language server package",
|
||||
command="npm install --prefix ./ @vtsls/language-server@0.2.9",
|
||||
command=build_npm_install_command("@vtsls/language-server", vtsls_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -15,7 +15,7 @@ from typing import Any
|
||||
from overrides import override
|
||||
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
from solidlsp.language_servers.typescript_language_server import (
|
||||
TypeScriptLanguageServer,
|
||||
prefer_non_node_modules_definition,
|
||||
@@ -407,31 +407,26 @@ class VueLanguageServer(SolidLanguageServer):
|
||||
typescript_language_server_version = typescript_config.get("typescript_language_server_version", "5.1.3")
|
||||
vue_config = solidlsp_settings.get_ls_specific_settings(Language.VUE)
|
||||
vue_language_server_version = vue_config.get("vue_language_server_version", "3.1.5")
|
||||
npm_registry = vue_config.get("npm_registry", typescript_config.get("npm_registry"))
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="vue-language-server",
|
||||
description="Vue language server package (Volar)",
|
||||
command=["npm", "install", "--prefix", "./", f"@vue/language-server@{vue_language_server_version}"],
|
||||
command=build_npm_install_command("@vue/language-server", vue_language_server_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="typescript",
|
||||
description="TypeScript (required for tsdk)",
|
||||
command=["npm", "install", "--prefix", "./", f"typescript@{typescript_version}"],
|
||||
command=build_npm_install_command("typescript", typescript_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="typescript-language-server",
|
||||
description="TypeScript language server (for Vue LS 3.x tsserver forwarding)",
|
||||
command=[
|
||||
"npm",
|
||||
"install",
|
||||
"--prefix",
|
||||
"./",
|
||||
f"typescript-language-server@{typescript_language_server_version}",
|
||||
],
|
||||
command=build_npm_install_command("typescript-language-server", typescript_language_server_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,7 +9,7 @@ import pathlib
|
||||
import shutil
|
||||
from typing import Any
|
||||
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, build_npm_install_command
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -66,13 +66,15 @@ class YamlLanguageServer(SolidLanguageServer):
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
yaml_language_server_version = self._custom_settings.get("yaml_language_server_version", "1.19.2")
|
||||
npm_registry = self._custom_settings.get("npm_registry")
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="yaml-language-server",
|
||||
description="yaml-language-server package (Red Hat)",
|
||||
command="npm install --prefix ./ yaml-language-server@1.19.2",
|
||||
command=build_npm_install_command("yaml-language-server", yaml_language_server_version, npm_registry),
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
|
||||
+169
-21
@@ -3,15 +3,19 @@ This file contains various utility functions like I/O operations, handling paths
|
||||
"""
|
||||
|
||||
import gzip
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import uuid
|
||||
import zipfile
|
||||
from enum import Enum
|
||||
from pathlib import Path, PurePath
|
||||
from typing import Literal, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import charset_normalizer
|
||||
import requests
|
||||
@@ -204,58 +208,106 @@ class FileUtils:
|
||||
"""
|
||||
Downloads the file from the given URL to the given {target_path}
|
||||
"""
|
||||
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
||||
FileUtils.download_file_verified(url, target_path)
|
||||
|
||||
@staticmethod
|
||||
def download_file_verified(
|
||||
url: str,
|
||||
target_path: str,
|
||||
expected_sha256: str | None = None,
|
||||
allowed_hosts: tuple[str, ...] | list[str] | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Downloads a file from ``url`` to ``target_path`` with optional integrity and host validation.
|
||||
"""
|
||||
# validating the requested host
|
||||
FileUtils._validate_download_host(url, allowed_hosts)
|
||||
|
||||
# streaming the download into a temporary file
|
||||
target_directory = os.path.dirname(target_path) or "."
|
||||
os.makedirs(target_directory, exist_ok=True)
|
||||
temp_file_path = str(PurePath(target_directory, f".{Path(target_path).name}.{uuid.uuid4().hex}.download"))
|
||||
response: requests.Response | None = None
|
||||
try:
|
||||
response = requests.get(url, stream=True, timeout=60)
|
||||
if response.status_code != 200:
|
||||
log.error(f"Error downloading file '{url}': {response.status_code} {response.text}")
|
||||
raise SolidLSPException("Error downloading file.")
|
||||
with open(target_path, "wb") as f:
|
||||
shutil.copyfileobj(response.raw, f)
|
||||
|
||||
FileUtils._validate_download_host(response.url, allowed_hosts)
|
||||
|
||||
with open(temp_file_path, "wb") as output_file:
|
||||
for chunk in response.iter_content(chunk_size=1024 * 1024):
|
||||
if chunk:
|
||||
output_file.write(chunk)
|
||||
|
||||
FileUtils._verify_sha256_if_configured(temp_file_path, expected_sha256)
|
||||
|
||||
os.replace(temp_file_path, target_path)
|
||||
except Exception as exc:
|
||||
log.error(f"Error downloading file '{url}': {exc}")
|
||||
raise SolidLSPException("Error downloading file.") from None
|
||||
finally:
|
||||
if response is not None:
|
||||
response.close()
|
||||
if os.path.exists(temp_file_path):
|
||||
Path.unlink(Path(temp_file_path))
|
||||
|
||||
@staticmethod
|
||||
def download_and_extract_archive(url: str, target_path: str, archive_type: str) -> None:
|
||||
"""
|
||||
Downloads the archive from the given URL having format {archive_type} and extracts it to the given {target_path}
|
||||
"""
|
||||
FileUtils.download_and_extract_archive_verified(url, target_path, archive_type)
|
||||
|
||||
@staticmethod
|
||||
def download_and_extract_archive_verified(
|
||||
url: str,
|
||||
target_path: str,
|
||||
archive_type: str,
|
||||
expected_sha256: str | None = None,
|
||||
allowed_hosts: tuple[str, ...] | list[str] | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Downloads an archive from ``url`` and extracts it safely into ``target_path``.
|
||||
"""
|
||||
try:
|
||||
tmp_files = []
|
||||
# preparing the temporary download location
|
||||
tmp_files: list[str] = []
|
||||
tmp_file_name = str(PurePath(os.path.expanduser("~"), "solidlsp_tmp", uuid.uuid4().hex))
|
||||
tmp_files.append(tmp_file_name)
|
||||
os.makedirs(os.path.dirname(tmp_file_name), exist_ok=True)
|
||||
FileUtils.download_file(url, tmp_file_name)
|
||||
|
||||
# downloading the archive with optional verification
|
||||
FileUtils.download_file_verified(url, tmp_file_name, expected_sha256=expected_sha256, allowed_hosts=allowed_hosts)
|
||||
tmp_files.append(tmp_file_name)
|
||||
|
||||
# extracting the archive according to its format
|
||||
if archive_type in ["tar", "gztar", "bztar", "xztar"]:
|
||||
os.makedirs(target_path, exist_ok=True)
|
||||
shutil.unpack_archive(tmp_file_name, target_path, archive_type)
|
||||
FileUtils._extract_tar_archive(tmp_file_name, target_path, archive_type)
|
||||
elif archive_type == "zip":
|
||||
os.makedirs(target_path, exist_ok=True)
|
||||
with zipfile.ZipFile(tmp_file_name, "r") as zip_ref:
|
||||
for zip_info in zip_ref.infolist():
|
||||
extracted_path = zip_ref.extract(zip_info, target_path)
|
||||
ZIP_SYSTEM_UNIX = 3 # zip file created on Unix system
|
||||
if zip_info.create_system != ZIP_SYSTEM_UNIX:
|
||||
continue
|
||||
# extractall() does not preserve permissions
|
||||
# see. https://github.com/python/cpython/issues/59999
|
||||
attrs = (zip_info.external_attr >> 16) & 0o777
|
||||
if attrs:
|
||||
os.chmod(extracted_path, attrs)
|
||||
FileUtils._extract_zip_archive(tmp_file_name, target_path)
|
||||
elif archive_type == "zip.gz":
|
||||
os.makedirs(target_path, exist_ok=True)
|
||||
tmp_file_name_ungzipped = tmp_file_name + ".zip"
|
||||
tmp_files.append(tmp_file_name_ungzipped)
|
||||
with gzip.open(tmp_file_name, "rb") as f_in, open(tmp_file_name_ungzipped, "wb") as f_out:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
shutil.unpack_archive(tmp_file_name_ungzipped, target_path, "zip")
|
||||
FileUtils._extract_zip_archive(tmp_file_name_ungzipped, target_path)
|
||||
elif archive_type == "gz":
|
||||
with gzip.open(tmp_file_name, "rb") as f_in, open(target_path, "wb") as f_out:
|
||||
target_directory = os.path.dirname(target_path) or "."
|
||||
os.makedirs(target_directory, exist_ok=True)
|
||||
temp_output_path = str(PurePath(target_directory, f".{Path(target_path).name}.{uuid.uuid4().hex}.extract"))
|
||||
tmp_files.append(temp_output_path)
|
||||
with gzip.open(tmp_file_name, "rb") as f_in, open(temp_output_path, "wb") as f_out:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
os.replace(temp_output_path, target_path)
|
||||
elif archive_type == "binary":
|
||||
# For single binary files, just move to target without extraction
|
||||
target_directory = os.path.dirname(target_path) or "."
|
||||
os.makedirs(target_directory, exist_ok=True)
|
||||
shutil.move(tmp_file_name, target_path)
|
||||
tmp_files.remove(tmp_file_name)
|
||||
else:
|
||||
log.error(f"Unknown archive type '{archive_type}' for extraction")
|
||||
raise SolidLSPException(f"Unknown archive type '{archive_type}'")
|
||||
@@ -267,6 +319,102 @@ class FileUtils:
|
||||
if os.path.exists(tmp_file_name):
|
||||
Path.unlink(Path(tmp_file_name))
|
||||
|
||||
@staticmethod
|
||||
def calculate_sha256(file_path: str) -> str:
|
||||
"""
|
||||
Calculates the SHA256 checksum of a file.
|
||||
"""
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(file_path, "rb") as input_file:
|
||||
for chunk in iter(lambda: input_file.read(8192), b""):
|
||||
sha256_hash.update(chunk)
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def _verify_sha256_if_configured(file_path: str, expected_sha256: str | None) -> None:
|
||||
"""
|
||||
Verifies the SHA256 checksum of a file when an expected value is provided.
|
||||
"""
|
||||
if expected_sha256 is None:
|
||||
return
|
||||
|
||||
actual_sha256 = FileUtils.calculate_sha256(file_path)
|
||||
if actual_sha256.lower() != expected_sha256.lower():
|
||||
raise SolidLSPException(f"Checksum verification failed for '{file_path}': expected {expected_sha256}, got {actual_sha256}")
|
||||
|
||||
@staticmethod
|
||||
def _validate_download_host(url: str, allowed_hosts: tuple[str, ...] | list[str] | None) -> None:
|
||||
"""
|
||||
Validates that a download URL resolves to one of the configured hosts.
|
||||
"""
|
||||
if not allowed_hosts:
|
||||
return
|
||||
|
||||
hostname = urlparse(url).hostname
|
||||
normalized_allowed_hosts = {host.lower() for host in allowed_hosts}
|
||||
if hostname is None or hostname.lower() not in normalized_allowed_hosts:
|
||||
raise SolidLSPException(
|
||||
f"Refusing to download from host '{hostname or '<unknown>'}'; allowed hosts: {sorted(normalized_allowed_hosts)}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _validate_extraction_path(member_name: str, target_path: str) -> str:
|
||||
"""
|
||||
Validates that an archive member stays within the extraction root and returns its destination path.
|
||||
"""
|
||||
normalized_parts = Path(member_name).parts
|
||||
if any(part == ".." for part in normalized_parts):
|
||||
raise SolidLSPException(f"Unsafe archive member '{member_name}': path traversal is not allowed")
|
||||
|
||||
absolute_target_path = os.path.abspath(target_path)
|
||||
absolute_member_path = os.path.abspath(os.path.join(target_path, member_name))
|
||||
if not (absolute_member_path.startswith(absolute_target_path + os.sep) or absolute_member_path == absolute_target_path):
|
||||
raise SolidLSPException(f"Unsafe archive member '{member_name}': path escapes extraction directory")
|
||||
|
||||
return absolute_member_path
|
||||
|
||||
@staticmethod
|
||||
def _extract_zip_archive(archive_path: str, target_path: str) -> None:
|
||||
"""
|
||||
Extracts a ZIP archive safely while preserving Unix permissions when available.
|
||||
"""
|
||||
with zipfile.ZipFile(archive_path, "r") as zip_ref:
|
||||
for zip_info in zip_ref.infolist():
|
||||
extracted_path = FileUtils._validate_extraction_path(zip_info.filename, target_path)
|
||||
|
||||
if zip_info.is_dir():
|
||||
os.makedirs(extracted_path, exist_ok=True)
|
||||
continue
|
||||
|
||||
os.makedirs(os.path.dirname(extracted_path), exist_ok=True)
|
||||
with zip_ref.open(zip_info, "r") as source_file, open(extracted_path, "wb") as output_file:
|
||||
shutil.copyfileobj(source_file, output_file)
|
||||
|
||||
ZIP_SYSTEM_UNIX = 3
|
||||
if zip_info.create_system == ZIP_SYSTEM_UNIX:
|
||||
attrs = (zip_info.external_attr >> 16) & 0o777
|
||||
if attrs:
|
||||
os.chmod(extracted_path, attrs)
|
||||
|
||||
@staticmethod
|
||||
def _extract_tar_archive(archive_path: str, target_path: str, archive_type: str) -> None:
|
||||
"""
|
||||
Extracts a tar archive safely into the target directory.
|
||||
"""
|
||||
archive_mode_by_type = {
|
||||
"tar": "r:",
|
||||
"gztar": "r:gz",
|
||||
"bztar": "r:bz2",
|
||||
"xztar": "r:xz",
|
||||
}
|
||||
tar_mode = cast(Literal["r:", "r:gz", "r:bz2", "r:xz"], archive_mode_by_type[archive_type])
|
||||
|
||||
with tarfile.open(archive_path, tar_mode) as tar_ref:
|
||||
for tar_member in tar_ref.getmembers():
|
||||
FileUtils._validate_extraction_path(tar_member.name, target_path)
|
||||
|
||||
tar_ref.extractall(target_path)
|
||||
|
||||
|
||||
class PlatformId(str, Enum):
|
||||
WIN_x86 = "win-x86"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for C# language server NuGet package download from NuGet.org."""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@@ -15,7 +16,7 @@ class TestNuGetOrgDownload:
|
||||
"""Test downloading Roslyn language server packages from NuGet.org."""
|
||||
|
||||
def test_download_nuget_package_uses_direct_url(self):
|
||||
"""Test that _download_nuget_package uses the URL from RuntimeDependency directly."""
|
||||
"""Test that _download_nuget_package uses the URL and checksum from RuntimeDependency directly."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Create a RuntimeDependency with a NuGet.org URL
|
||||
test_dependency = RuntimeDependency(
|
||||
@@ -41,21 +42,38 @@ class TestNuGetOrgDownload:
|
||||
repository_root_path="/fake/repo",
|
||||
)
|
||||
|
||||
# Mock urllib.request.urlretrieve to capture the URL being used
|
||||
with patch("solidlsp.language_servers.csharp_language_server.urllib.request.urlretrieve") as mock_retrieve:
|
||||
with patch("solidlsp.language_servers.csharp_language_server.SafeZipExtractor"):
|
||||
try:
|
||||
dependency_provider._download_nuget_package(test_dependency)
|
||||
except Exception:
|
||||
# Expected to fail since we're mocking, but we want to check the URL
|
||||
pass
|
||||
captured_calls: list[tuple[str, str, str, str | None, tuple[str, ...] | list[str] | None]] = []
|
||||
|
||||
# Verify that urlretrieve was called with the NuGet.org URL
|
||||
assert mock_retrieve.called, "urlretrieve should be called"
|
||||
called_url = mock_retrieve.call_args[0][0]
|
||||
assert called_url == test_dependency.url, f"Should use URL from RuntimeDependency: {test_dependency.url}"
|
||||
assert "nuget.org" in called_url, "Should use NuGet.org URL"
|
||||
assert "azure" not in called_url.lower(), "Should not use Azure feed"
|
||||
def fake_download_and_extract(
|
||||
url: str,
|
||||
target_path: str,
|
||||
archive_type: str,
|
||||
expected_sha256: str | None = None,
|
||||
allowed_hosts: tuple[str, ...] | list[str] | None = None,
|
||||
) -> None:
|
||||
captured_calls.append((url, target_path, archive_type, expected_sha256, allowed_hosts))
|
||||
Path(target_path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with patch(
|
||||
"solidlsp.language_servers.csharp_language_server.FileUtils.download_and_extract_archive_verified",
|
||||
side_effect=fake_download_and_extract,
|
||||
):
|
||||
package_dir = dependency_provider._download_nuget_package(test_dependency)
|
||||
|
||||
assert package_dir == Path(temp_dir) / "temp_downloads" / "roslyn-language-server.linux-x64.5.5.0-2.26078.4"
|
||||
assert captured_calls == [
|
||||
(
|
||||
test_dependency.url,
|
||||
str(package_dir),
|
||||
"zip",
|
||||
test_dependency.sha256,
|
||||
test_dependency.allowed_hosts,
|
||||
)
|
||||
]
|
||||
called_url = captured_calls[0][0]
|
||||
assert called_url == test_dependency.url, f"Should use URL from RuntimeDependency: {test_dependency.url}"
|
||||
assert "nuget.org" in called_url, "Should use NuGet.org URL"
|
||||
assert "azure" not in called_url.lower(), "Should not use Azure feed"
|
||||
|
||||
def test_runtime_dependencies_use_nuget_org_urls(self):
|
||||
"""Test that _RUNTIME_DEPENDENCIES are configured with NuGet.org URLs."""
|
||||
@@ -106,13 +124,7 @@ class TestNuGetOrgDownload:
|
||||
)
|
||||
|
||||
# Mock urllib.request.urlopen to track if Azure feed is accessed
|
||||
with patch("solidlsp.language_servers.csharp_language_server.urllib.request.urlopen") as mock_urlopen:
|
||||
with patch("solidlsp.language_servers.csharp_language_server.urllib.request.urlretrieve"):
|
||||
with patch("solidlsp.language_servers.csharp_language_server.SafeZipExtractor"):
|
||||
try:
|
||||
dependency_provider._download_nuget_package(test_dependency)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Verify that urlopen was NOT called (no service index lookup)
|
||||
assert not mock_urlopen.called, "Should not call urlopen for Azure service index lookup"
|
||||
with patch(
|
||||
"solidlsp.language_servers.csharp_language_server.FileUtils.download_and_extract_archive_verified",
|
||||
):
|
||||
dependency_provider._download_nuget_package(test_dependency)
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Tests for the Luau language server dependency provider."""
|
||||
|
||||
import io
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -21,23 +19,6 @@ def _make_provider(
|
||||
)
|
||||
|
||||
|
||||
class _FakeResponse:
|
||||
def __init__(self, content: bytes) -> None:
|
||||
self.content = content
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
return
|
||||
|
||||
def iter_content(self, chunk_size: int = 8192):
|
||||
yield self.content
|
||||
|
||||
def __enter__(self) -> "_FakeResponse":
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> None:
|
||||
return None
|
||||
|
||||
|
||||
@pytest.mark.luau
|
||||
class TestLuauDependencyProvider:
|
||||
def test_create_launch_command_uses_ls_path_override_and_adds_assets(self, tmp_path: Path) -> None:
|
||||
@@ -110,13 +91,24 @@ class TestLuauDependencyProvider:
|
||||
def test_download_luau_lsp_extracts_binary_into_ls_resources_dir(self, tmp_path: Path) -> None:
|
||||
provider = _make_provider(tmp_path)
|
||||
|
||||
archive = io.BytesIO()
|
||||
with zipfile.ZipFile(archive, "w") as zip_file:
|
||||
zip_file.writestr("nested/luau-lsp", "#!/bin/sh\n")
|
||||
def fake_extract(
|
||||
url: str,
|
||||
target_path: str,
|
||||
archive_type: str,
|
||||
expected_sha256: str | None = None,
|
||||
allowed_hosts: tuple[str, ...] | list[str] | None = None,
|
||||
) -> None:
|
||||
del url, archive_type, expected_sha256, allowed_hosts
|
||||
nested_dir = Path(target_path) / "nested"
|
||||
nested_dir.mkdir(parents=True, exist_ok=True)
|
||||
(nested_dir / "luau-lsp").write_text("#!/bin/sh\n", encoding="utf-8")
|
||||
|
||||
with patch("solidlsp.language_servers.luau_lsp.platform.system", return_value="Linux"):
|
||||
with patch("solidlsp.language_servers.luau_lsp.platform.machine", return_value="aarch64"):
|
||||
with patch("solidlsp.language_servers.luau_lsp.requests.get", return_value=_FakeResponse(archive.getvalue())):
|
||||
with patch(
|
||||
"solidlsp.language_servers.luau_lsp.FileUtils.download_and_extract_archive_verified",
|
||||
side_effect=fake_extract,
|
||||
):
|
||||
binary_path = provider._download_luau_lsp()
|
||||
|
||||
resolved_binary = Path(binary_path)
|
||||
@@ -127,10 +119,16 @@ class TestLuauDependencyProvider:
|
||||
def test_download_roblox_support_files_writes_into_ls_resources_dir(self, tmp_path: Path) -> None:
|
||||
provider = _make_provider(tmp_path)
|
||||
|
||||
with patch(
|
||||
"solidlsp.language_servers.luau_lsp.requests.get",
|
||||
side_effect=[_FakeResponse(b"types"), _FakeResponse(b"docs")],
|
||||
):
|
||||
def fake_download(
|
||||
url: str, target_path: str, expected_sha256: str | None = None, allowed_hosts: tuple[str, ...] | list[str] | None = None
|
||||
) -> None:
|
||||
del expected_sha256, allowed_hosts
|
||||
if "type-definitions" in url:
|
||||
Path(target_path).write_bytes(b"types")
|
||||
else:
|
||||
Path(target_path).write_bytes(b"docs")
|
||||
|
||||
with patch("solidlsp.language_servers.luau_lsp.FileUtils.download_file_verified", side_effect=fake_download):
|
||||
definitions_path, docs_path = provider._download_roblox_support_files("LocalUserSecurity")
|
||||
|
||||
assert definitions_path == str(tmp_path / "globalTypes.LocalUserSecurity.d.luau")
|
||||
@@ -141,7 +139,10 @@ class TestLuauDependencyProvider:
|
||||
def test_download_standard_docs_writes_into_ls_resources_dir(self, tmp_path: Path) -> None:
|
||||
provider = _make_provider(tmp_path, {"platform": "standard"})
|
||||
|
||||
with patch("solidlsp.language_servers.luau_lsp.requests.get", return_value=_FakeResponse(b"docs")):
|
||||
with patch(
|
||||
"solidlsp.language_servers.luau_lsp.FileUtils.download_file_verified",
|
||||
side_effect=lambda url, target_path, expected_sha256=None, allowed_hosts=None: Path(target_path).write_bytes(b"docs"),
|
||||
):
|
||||
docs_path = provider._download_standard_docs()
|
||||
|
||||
assert docs_path == str(tmp_path / "luau-en-us.json")
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
|
||||
|
||||
class _FakeResponse:
|
||||
def __init__(self, payload: bytes, final_url: str) -> None:
|
||||
self.status_code = 200
|
||||
self.headers = {"content-encoding": "gzip"}
|
||||
self.url = final_url
|
||||
self._payload = payload
|
||||
|
||||
def iter_content(self, chunk_size: int = 1):
|
||||
for offset in range(0, len(self._payload), chunk_size):
|
||||
yield self._payload[offset : offset + chunk_size]
|
||||
|
||||
def close(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def test_download_file_verified_writes_decoded_response_body(tmp_path: Path) -> None:
|
||||
"""Gzip-encoded transfer bodies should be written as decoded payload bytes."""
|
||||
payload = b"PK\x03\x04zip-content"
|
||||
target_path = tmp_path / "downloaded.vsix"
|
||||
final_url = "https://marketplace.visualstudio.com/example.vsix"
|
||||
|
||||
with patch(
|
||||
"solidlsp.ls_utils.requests.get",
|
||||
return_value=_FakeResponse(payload, final_url),
|
||||
):
|
||||
FileUtils.download_file_verified(
|
||||
"https://marketplace.visualstudio.com/example.vsix",
|
||||
str(target_path),
|
||||
expected_sha256=hashlib.sha256(payload).hexdigest(),
|
||||
allowed_hosts=("marketplace.visualstudio.com",),
|
||||
)
|
||||
|
||||
assert target_path.read_bytes() == payload
|
||||
Reference in new issue
Block a user