#
tokens: 48307/50000 15/413 files (page 6/21)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 6 of 21. Use http://codebase.md/oraios/serena?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .devcontainer
│   └── devcontainer.json
├── .dockerignore
├── .env.example
├── .github
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── config.yml
│   │   ├── feature_request.md
│   │   └── issue--bug--performance-problem--question-.md
│   └── workflows
│       ├── codespell.yml
│       ├── docker.yml
│       ├── docs.yaml
│       ├── junie.yml
│       ├── publish.yml
│       └── pytest.yml
├── .gitignore
├── .serena
│   ├── .gitignore
│   ├── memories
│   │   ├── adding_new_language_support_guide.md
│   │   ├── serena_core_concepts_and_architecture.md
│   │   ├── serena_repository_structure.md
│   │   └── suggested_commands.md
│   └── project.yml
├── .vscode
│   └── settings.json
├── CHANGELOG.md
├── CLAUDE.md
├── compose.yaml
├── CONTRIBUTING.md
├── docker_build_and_run.sh
├── DOCKER.md
├── Dockerfile
├── docs
│   ├── _config.yml
│   ├── _static
│   │   └── images
│   │       └── jetbrains-marketplace-button.png
│   ├── .gitignore
│   ├── 01-about
│   │   ├── 000_intro.md
│   │   ├── 010_llm-integration.md
│   │   ├── 020_programming-languages.md
│   │   ├── 030_serena-in-action.md
│   │   ├── 035_tools.md
│   │   ├── 040_comparison-to-other-agents.md
│   │   └── 050_acknowledgements.md
│   ├── 02-usage
│   │   ├── 000_intro.md
│   │   ├── 010_prerequisites.md
│   │   ├── 020_running.md
│   │   ├── 025_jetbrains_plugin.md
│   │   ├── 030_clients.md
│   │   ├── 040_workflow.md
│   │   ├── 050_configuration.md
│   │   ├── 060_dashboard.md
│   │   ├── 070_security.md
│   │   └── 999_additional-usage.md
│   ├── 03-special-guides
│   │   ├── 000_intro.md
│   │   ├── custom_agent.md
│   │   ├── groovy_setup_guide_for_serena.md
│   │   ├── scala_setup_guide_for_serena.md
│   │   └── serena_on_chatgpt.md
│   ├── autogen_rst.py
│   ├── create_toc.py
│   └── index.md
├── flake.lock
├── flake.nix
├── lessons_learned.md
├── LICENSE
├── llms-install.md
├── pyproject.toml
├── README.md
├── repo_dir_sync.py
├── resources
│   ├── jetbrains-marketplace-button.cdr
│   ├── serena-icons.cdr
│   ├── serena-logo-dark-mode.svg
│   ├── serena-logo.cdr
│   ├── serena-logo.svg
│   └── vscode_sponsor_logo.png
├── roadmap.md
├── scripts
│   ├── agno_agent.py
│   ├── demo_run_tools.py
│   ├── gen_prompt_factory.py
│   ├── mcp_server.py
│   ├── print_mode_context_options.py
│   ├── print_tool_overview.py
│   └── profile_tool_call.py
├── src
│   ├── interprompt
│   │   ├── __init__.py
│   │   ├── .syncCommitId.remote
│   │   ├── .syncCommitId.this
│   │   ├── jinja_template.py
│   │   ├── multilang_prompt.py
│   │   ├── prompt_factory.py
│   │   └── util
│   │       ├── __init__.py
│   │       └── class_decorators.py
│   ├── README.md
│   ├── serena
│   │   ├── __init__.py
│   │   ├── agent.py
│   │   ├── agno.py
│   │   ├── analytics.py
│   │   ├── cli.py
│   │   ├── code_editor.py
│   │   ├── config
│   │   │   ├── __init__.py
│   │   │   ├── context_mode.py
│   │   │   └── serena_config.py
│   │   ├── constants.py
│   │   ├── dashboard.py
│   │   ├── generated
│   │   │   └── generated_prompt_factory.py
│   │   ├── gui_log_viewer.py
│   │   ├── ls_manager.py
│   │   ├── mcp.py
│   │   ├── project.py
│   │   ├── prompt_factory.py
│   │   ├── resources
│   │   │   ├── config
│   │   │   │   ├── contexts
│   │   │   │   │   ├── agent.yml
│   │   │   │   │   ├── chatgpt.yml
│   │   │   │   │   ├── claude-code.yml
│   │   │   │   │   ├── codex.yml
│   │   │   │   │   ├── context.template.yml
│   │   │   │   │   ├── desktop-app.yml
│   │   │   │   │   ├── ide.yml
│   │   │   │   │   └── oaicompat-agent.yml
│   │   │   │   ├── internal_modes
│   │   │   │   │   └── jetbrains.yml
│   │   │   │   ├── modes
│   │   │   │   │   ├── editing.yml
│   │   │   │   │   ├── interactive.yml
│   │   │   │   │   ├── mode.template.yml
│   │   │   │   │   ├── no-memories.yml
│   │   │   │   │   ├── no-onboarding.yml
│   │   │   │   │   ├── onboarding.yml
│   │   │   │   │   ├── one-shot.yml
│   │   │   │   │   └── planning.yml
│   │   │   │   └── prompt_templates
│   │   │   │       ├── simple_tool_outputs.yml
│   │   │   │       └── system_prompt.yml
│   │   │   ├── dashboard
│   │   │   │   ├── dashboard.css
│   │   │   │   ├── dashboard.js
│   │   │   │   ├── index.html
│   │   │   │   ├── jquery.min.js
│   │   │   │   ├── news
│   │   │   │   │   └── 20260111.html
│   │   │   │   ├── serena-icon-16.png
│   │   │   │   ├── serena-icon-32.png
│   │   │   │   ├── serena-icon-48.png
│   │   │   │   ├── serena-logo-dark-mode.svg
│   │   │   │   ├── serena-logo.svg
│   │   │   │   ├── serena-logs-dark-mode.png
│   │   │   │   └── serena-logs.png
│   │   │   ├── project.template.yml
│   │   │   └── serena_config.template.yml
│   │   ├── symbol.py
│   │   ├── task_executor.py
│   │   ├── text_utils.py
│   │   ├── tools
│   │   │   ├── __init__.py
│   │   │   ├── cmd_tools.py
│   │   │   ├── config_tools.py
│   │   │   ├── file_tools.py
│   │   │   ├── jetbrains_plugin_client.py
│   │   │   ├── jetbrains_tools.py
│   │   │   ├── jetbrains_types.py
│   │   │   ├── memory_tools.py
│   │   │   ├── symbol_tools.py
│   │   │   ├── tools_base.py
│   │   │   └── workflow_tools.py
│   │   └── util
│   │       ├── class_decorators.py
│   │       ├── cli_util.py
│   │       ├── exception.py
│   │       ├── file_system.py
│   │       ├── general.py
│   │       ├── git.py
│   │       ├── gui.py
│   │       ├── inspection.py
│   │       ├── logging.py
│   │       ├── shell.py
│   │       ├── thread.py
│   │       └── version.py
│   └── solidlsp
│       ├── __init__.py
│       ├── .gitignore
│       ├── language_servers
│       │   ├── al_language_server.py
│       │   ├── bash_language_server.py
│       │   ├── clangd_language_server.py
│       │   ├── clojure_lsp.py
│       │   ├── common.py
│       │   ├── csharp_language_server.py
│       │   ├── dart_language_server.py
│       │   ├── eclipse_jdtls.py
│       │   ├── elixir_tools
│       │   │   ├── __init__.py
│       │   │   ├── elixir_tools.py
│       │   │   └── README.md
│       │   ├── elm_language_server.py
│       │   ├── erlang_language_server.py
│       │   ├── fortran_language_server.py
│       │   ├── fsharp_language_server.py
│       │   ├── gopls.py
│       │   ├── groovy_language_server.py
│       │   ├── haskell_language_server.py
│       │   ├── intelephense.py
│       │   ├── jedi_server.py
│       │   ├── julia_server.py
│       │   ├── kotlin_language_server.py
│       │   ├── lua_ls.py
│       │   ├── marksman.py
│       │   ├── matlab_language_server.py
│       │   ├── nixd_ls.py
│       │   ├── omnisharp
│       │   │   ├── initialize_params.json
│       │   │   ├── runtime_dependencies.json
│       │   │   └── workspace_did_change_configuration.json
│       │   ├── omnisharp.py
│       │   ├── pascal_server.py
│       │   ├── perl_language_server.py
│       │   ├── powershell_language_server.py
│       │   ├── pyright_server.py
│       │   ├── r_language_server.py
│       │   ├── regal_server.py
│       │   ├── ruby_lsp.py
│       │   ├── rust_analyzer.py
│       │   ├── scala_language_server.py
│       │   ├── solargraph.py
│       │   ├── sourcekit_lsp.py
│       │   ├── taplo_server.py
│       │   ├── terraform_ls.py
│       │   ├── typescript_language_server.py
│       │   ├── vts_language_server.py
│       │   ├── vue_language_server.py
│       │   ├── yaml_language_server.py
│       │   └── zls.py
│       ├── ls_config.py
│       ├── ls_exceptions.py
│       ├── ls_handler.py
│       ├── ls_request.py
│       ├── ls_types.py
│       ├── ls_utils.py
│       ├── ls.py
│       ├── lsp_protocol_handler
│       │   ├── lsp_constants.py
│       │   ├── lsp_requests.py
│       │   ├── lsp_types.py
│       │   └── server.py
│       ├── settings.py
│       └── util
│           ├── cache.py
│           ├── subprocess_util.py
│           └── zip.py
├── sync.py
├── test
│   ├── __init__.py
│   ├── conftest.py
│   ├── resources
│   │   └── repos
│   │       ├── al
│   │       │   └── test_repo
│   │       │       ├── app.json
│   │       │       └── src
│   │       │           ├── Codeunits
│   │       │           │   ├── CustomerMgt.Codeunit.al
│   │       │           │   └── PaymentProcessorImpl.Codeunit.al
│   │       │           ├── Enums
│   │       │           │   └── CustomerType.Enum.al
│   │       │           ├── Interfaces
│   │       │           │   └── IPaymentProcessor.Interface.al
│   │       │           ├── Pages
│   │       │           │   ├── CustomerCard.Page.al
│   │       │           │   └── CustomerList.Page.al
│   │       │           ├── TableExtensions
│   │       │           │   └── Item.TableExt.al
│   │       │           └── Tables
│   │       │               └── Customer.Table.al
│   │       ├── bash
│   │       │   └── test_repo
│   │       │       ├── config.sh
│   │       │       ├── main.sh
│   │       │       └── utils.sh
│   │       ├── clojure
│   │       │   └── test_repo
│   │       │       ├── deps.edn
│   │       │       └── src
│   │       │           └── test_app
│   │       │               ├── core.clj
│   │       │               └── utils.clj
│   │       ├── csharp
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── Models
│   │       │       │   └── Person.cs
│   │       │       ├── Program.cs
│   │       │       ├── serena.sln
│   │       │       └── TestProject.csproj
│   │       ├── dart
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── lib
│   │       │       │   ├── helper.dart
│   │       │       │   ├── main.dart
│   │       │       │   └── models.dart
│   │       │       └── pubspec.yaml
│   │       ├── elixir
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── lib
│   │       │       │   ├── examples.ex
│   │       │       │   ├── ignored_dir
│   │       │       │   │   └── ignored_module.ex
│   │       │       │   ├── models.ex
│   │       │       │   ├── services.ex
│   │       │       │   ├── test_repo.ex
│   │       │       │   └── utils.ex
│   │       │       ├── mix.exs
│   │       │       ├── mix.lock
│   │       │       ├── scripts
│   │       │       │   └── build_script.ex
│   │       │       └── test
│   │       │           ├── models_test.exs
│   │       │           └── test_repo_test.exs
│   │       ├── elm
│   │       │   └── test_repo
│   │       │       ├── elm.json
│   │       │       ├── Main.elm
│   │       │       └── Utils.elm
│   │       ├── erlang
│   │       │   └── test_repo
│   │       │       ├── hello.erl
│   │       │       ├── ignored_dir
│   │       │       │   └── ignored_module.erl
│   │       │       ├── include
│   │       │       │   ├── records.hrl
│   │       │       │   └── types.hrl
│   │       │       ├── math_utils.erl
│   │       │       ├── rebar.config
│   │       │       ├── src
│   │       │       │   ├── app.erl
│   │       │       │   ├── models.erl
│   │       │       │   ├── services.erl
│   │       │       │   └── utils.erl
│   │       │       └── test
│   │       │           ├── models_tests.erl
│   │       │           └── utils_tests.erl
│   │       ├── fortran
│   │       │   └── test_repo
│   │       │       ├── main.f90
│   │       │       └── modules
│   │       │           ├── geometry.f90
│   │       │           └── math_utils.f90
│   │       ├── fsharp
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── Calculator.fs
│   │       │       ├── Models
│   │       │       │   └── Person.fs
│   │       │       ├── Program.fs
│   │       │       ├── README.md
│   │       │       └── TestProject.fsproj
│   │       ├── go
│   │       │   └── test_repo
│   │       │       └── main.go
│   │       ├── groovy
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── build.gradle
│   │       │       └── src
│   │       │           └── main
│   │       │               └── groovy
│   │       │                   └── com
│   │       │                       └── example
│   │       │                           ├── Main.groovy
│   │       │                           ├── Model.groovy
│   │       │                           ├── ModelUser.groovy
│   │       │                           └── Utils.groovy
│   │       ├── haskell
│   │       │   └── test_repo
│   │       │       ├── app
│   │       │       │   └── Main.hs
│   │       │       ├── haskell-test-repo.cabal
│   │       │       ├── package.yaml
│   │       │       ├── src
│   │       │       │   ├── Calculator.hs
│   │       │       │   └── Helper.hs
│   │       │       └── stack.yaml
│   │       ├── java
│   │       │   └── test_repo
│   │       │       ├── pom.xml
│   │       │       └── src
│   │       │           └── main
│   │       │               └── java
│   │       │                   └── test_repo
│   │       │                       ├── Main.java
│   │       │                       ├── Model.java
│   │       │                       ├── ModelUser.java
│   │       │                       └── Utils.java
│   │       ├── julia
│   │       │   └── test_repo
│   │       │       ├── lib
│   │       │       │   └── helper.jl
│   │       │       └── main.jl
│   │       ├── kotlin
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── build.gradle.kts
│   │       │       └── src
│   │       │           └── main
│   │       │               └── kotlin
│   │       │                   └── test_repo
│   │       │                       ├── Main.kt
│   │       │                       ├── Model.kt
│   │       │                       ├── ModelUser.kt
│   │       │                       └── Utils.kt
│   │       ├── lua
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── main.lua
│   │       │       ├── src
│   │       │       │   ├── calculator.lua
│   │       │       │   └── utils.lua
│   │       │       └── tests
│   │       │           └── test_calculator.lua
│   │       ├── markdown
│   │       │   └── test_repo
│   │       │       ├── api.md
│   │       │       ├── CONTRIBUTING.md
│   │       │       ├── guide.md
│   │       │       └── README.md
│   │       ├── matlab
│   │       │   └── test_repo
│   │       │       ├── Calculator.m
│   │       │       └── main.m
│   │       ├── nix
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── default.nix
│   │       │       ├── flake.nix
│   │       │       ├── lib
│   │       │       │   └── utils.nix
│   │       │       ├── modules
│   │       │       │   └── example.nix
│   │       │       └── scripts
│   │       │           └── hello.sh
│   │       ├── pascal
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── lib
│   │       │       │   └── helper.pas
│   │       │       └── main.pas
│   │       ├── perl
│   │       │   └── test_repo
│   │       │       ├── helper.pl
│   │       │       └── main.pl
│   │       ├── php
│   │       │   └── test_repo
│   │       │       ├── helper.php
│   │       │       ├── index.php
│   │       │       └── simple_var.php
│   │       ├── powershell
│   │       │   └── test_repo
│   │       │       ├── main.ps1
│   │       │       ├── PowerShellEditorServices.json
│   │       │       └── utils.ps1
│   │       ├── python
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── custom_test
│   │       │       │   ├── __init__.py
│   │       │       │   └── advanced_features.py
│   │       │       ├── examples
│   │       │       │   ├── __init__.py
│   │       │       │   └── user_management.py
│   │       │       ├── ignore_this_dir_with_postfix
│   │       │       │   └── ignored_module.py
│   │       │       ├── scripts
│   │       │       │   ├── __init__.py
│   │       │       │   └── run_app.py
│   │       │       └── test_repo
│   │       │           ├── __init__.py
│   │       │           ├── complex_types.py
│   │       │           ├── models.py
│   │       │           ├── name_collisions.py
│   │       │           ├── nested_base.py
│   │       │           ├── nested.py
│   │       │           ├── overloaded.py
│   │       │           ├── services.py
│   │       │           ├── utils.py
│   │       │           └── variables.py
│   │       ├── r
│   │       │   └── test_repo
│   │       │       ├── .Rbuildignore
│   │       │       ├── DESCRIPTION
│   │       │       ├── examples
│   │       │       │   └── analysis.R
│   │       │       ├── NAMESPACE
│   │       │       └── R
│   │       │           ├── models.R
│   │       │           └── utils.R
│   │       ├── rego
│   │       │   └── test_repo
│   │       │       ├── policies
│   │       │       │   ├── authz.rego
│   │       │       │   └── validation.rego
│   │       │       └── utils
│   │       │           └── helpers.rego
│   │       ├── ruby
│   │       │   └── test_repo
│   │       │       ├── .solargraph.yml
│   │       │       ├── examples
│   │       │       │   └── user_management.rb
│   │       │       ├── lib.rb
│   │       │       ├── main.rb
│   │       │       ├── models.rb
│   │       │       ├── nested.rb
│   │       │       ├── services.rb
│   │       │       └── variables.rb
│   │       ├── rust
│   │       │   ├── test_repo
│   │       │   │   ├── Cargo.lock
│   │       │   │   ├── Cargo.toml
│   │       │   │   └── src
│   │       │   │       ├── lib.rs
│   │       │   │       └── main.rs
│   │       │   └── test_repo_2024
│   │       │       ├── Cargo.lock
│   │       │       ├── Cargo.toml
│   │       │       └── src
│   │       │           ├── lib.rs
│   │       │           └── main.rs
│   │       ├── scala
│   │       │   ├── build.sbt
│   │       │   ├── project
│   │       │   │   ├── build.properties
│   │       │   │   ├── metals.sbt
│   │       │   │   └── plugins.sbt
│   │       │   └── src
│   │       │       └── main
│   │       │           └── scala
│   │       │               └── com
│   │       │                   └── example
│   │       │                       ├── Main.scala
│   │       │                       └── Utils.scala
│   │       ├── swift
│   │       │   └── test_repo
│   │       │       ├── Package.swift
│   │       │       └── src
│   │       │           ├── main.swift
│   │       │           └── utils.swift
│   │       ├── terraform
│   │       │   └── test_repo
│   │       │       ├── data.tf
│   │       │       ├── main.tf
│   │       │       ├── outputs.tf
│   │       │       └── variables.tf
│   │       ├── toml
│   │       │   └── test_repo
│   │       │       ├── Cargo.toml
│   │       │       ├── config.toml
│   │       │       └── pyproject.toml
│   │       ├── typescript
│   │       │   └── test_repo
│   │       │       ├── .serena
│   │       │       │   └── project.yml
│   │       │       ├── index.ts
│   │       │       ├── tsconfig.json
│   │       │       ├── use_helper.ts
│   │       │       └── ws_manager.js
│   │       ├── vue
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── index.html
│   │       │       ├── package.json
│   │       │       ├── src
│   │       │       │   ├── App.vue
│   │       │       │   ├── components
│   │       │       │   │   ├── CalculatorButton.vue
│   │       │       │   │   ├── CalculatorDisplay.vue
│   │       │       │   │   └── CalculatorInput.vue
│   │       │       │   ├── composables
│   │       │       │   │   ├── useFormatter.ts
│   │       │       │   │   └── useTheme.ts
│   │       │       │   ├── main.ts
│   │       │       │   ├── stores
│   │       │       │   │   └── calculator.ts
│   │       │       │   └── types
│   │       │       │       └── index.ts
│   │       │       ├── tsconfig.json
│   │       │       ├── tsconfig.node.json
│   │       │       └── vite.config.ts
│   │       ├── yaml
│   │       │   └── test_repo
│   │       │       ├── config.yaml
│   │       │       ├── data.yaml
│   │       │       └── services.yml
│   │       └── zig
│   │           └── test_repo
│   │               ├── .gitignore
│   │               ├── build.zig
│   │               ├── src
│   │               │   ├── calculator.zig
│   │               │   ├── main.zig
│   │               │   └── math_utils.zig
│   │               └── zls.json
│   ├── serena
│   │   ├── __init__.py
│   │   ├── __snapshots__
│   │   │   └── test_symbol_editing.ambr
│   │   ├── config
│   │   │   ├── __init__.py
│   │   │   └── test_serena_config.py
│   │   ├── test_cli_project_commands.py
│   │   ├── test_edit_marker.py
│   │   ├── test_mcp.py
│   │   ├── test_serena_agent.py
│   │   ├── test_symbol_editing.py
│   │   ├── test_symbol.py
│   │   ├── test_task_executor.py
│   │   ├── test_text_utils.py
│   │   ├── test_tool_parameter_types.py
│   │   └── util
│   │       ├── test_exception.py
│   │       └── test_file_system.py
│   └── solidlsp
│       ├── al
│       │   └── test_al_basic.py
│       ├── bash
│       │   ├── __init__.py
│       │   └── test_bash_basic.py
│       ├── clojure
│       │   ├── __init__.py
│       │   └── test_clojure_basic.py
│       ├── csharp
│       │   └── test_csharp_basic.py
│       ├── dart
│       │   ├── __init__.py
│       │   └── test_dart_basic.py
│       ├── elixir
│       │   ├── __init__.py
│       │   ├── conftest.py
│       │   ├── test_elixir_basic.py
│       │   ├── test_elixir_ignored_dirs.py
│       │   ├── test_elixir_integration.py
│       │   └── test_elixir_symbol_retrieval.py
│       ├── elm
│       │   └── test_elm_basic.py
│       ├── erlang
│       │   ├── __init__.py
│       │   ├── conftest.py
│       │   ├── test_erlang_basic.py
│       │   ├── test_erlang_ignored_dirs.py
│       │   └── test_erlang_symbol_retrieval.py
│       ├── fortran
│       │   ├── __init__.py
│       │   └── test_fortran_basic.py
│       ├── fsharp
│       │   └── test_fsharp_basic.py
│       ├── go
│       │   └── test_go_basic.py
│       ├── groovy
│       │   └── test_groovy_basic.py
│       ├── haskell
│       │   ├── __init__.py
│       │   └── test_haskell_basic.py
│       ├── java
│       │   └── test_java_basic.py
│       ├── julia
│       │   └── test_julia_basic.py
│       ├── kotlin
│       │   └── test_kotlin_basic.py
│       ├── lua
│       │   └── test_lua_basic.py
│       ├── markdown
│       │   ├── __init__.py
│       │   └── test_markdown_basic.py
│       ├── matlab
│       │   ├── __init__.py
│       │   └── test_matlab_basic.py
│       ├── nix
│       │   └── test_nix_basic.py
│       ├── pascal
│       │   ├── __init__.py
│       │   └── test_pascal_basic.py
│       ├── perl
│       │   └── test_perl_basic.py
│       ├── php
│       │   └── test_php_basic.py
│       ├── powershell
│       │   ├── __init__.py
│       │   └── test_powershell_basic.py
│       ├── python
│       │   ├── test_python_basic.py
│       │   ├── test_retrieval_with_ignored_dirs.py
│       │   └── test_symbol_retrieval.py
│       ├── r
│       │   ├── __init__.py
│       │   └── test_r_basic.py
│       ├── rego
│       │   └── test_rego_basic.py
│       ├── ruby
│       │   ├── test_ruby_basic.py
│       │   └── test_ruby_symbol_retrieval.py
│       ├── rust
│       │   ├── test_rust_2024_edition.py
│       │   ├── test_rust_analyzer_detection.py
│       │   └── test_rust_basic.py
│       ├── scala
│       │   └── test_scala_language_server.py
│       ├── swift
│       │   └── test_swift_basic.py
│       ├── terraform
│       │   └── test_terraform_basic.py
│       ├── test_lsp_protocol_handler_server.py
│       ├── toml
│       │   ├── __init__.py
│       │   ├── test_toml_basic.py
│       │   ├── test_toml_edge_cases.py
│       │   ├── test_toml_ignored_dirs.py
│       │   └── test_toml_symbol_retrieval.py
│       ├── typescript
│       │   └── test_typescript_basic.py
│       ├── util
│       │   └── test_zip.py
│       ├── vue
│       │   ├── __init__.py
│       │   ├── test_vue_basic.py
│       │   ├── test_vue_error_cases.py
│       │   ├── test_vue_rename.py
│       │   └── test_vue_symbol_retrieval.py
│       ├── yaml_ls
│       │   ├── __init__.py
│       │   └── test_yaml_basic.py
│       └── zig
│           └── test_zig_basic.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/src/serena/ls_manager.py:
--------------------------------------------------------------------------------

```python
  1 | import logging
  2 | import threading
  3 | from collections.abc import Iterator
  4 | 
  5 | from sensai.util.logging import LogTime
  6 | 
  7 | from serena.config.serena_config import SerenaPaths
  8 | from serena.constants import SERENA_MANAGED_DIR_NAME
  9 | from solidlsp import SolidLanguageServer
 10 | from solidlsp.ls_config import Language, LanguageServerConfig
 11 | from solidlsp.settings import SolidLSPSettings
 12 | 
 13 | log = logging.getLogger(__name__)
 14 | 
 15 | 
 16 | class LanguageServerFactory:
 17 |     def __init__(
 18 |         self,
 19 |         project_root: str,
 20 |         encoding: str,
 21 |         ignored_patterns: list[str],
 22 |         ls_timeout: float | None = None,
 23 |         ls_specific_settings: dict | None = None,
 24 |         trace_lsp_communication: bool = False,
 25 |     ):
 26 |         self.project_root = project_root
 27 |         self.encoding = encoding
 28 |         self.ignored_patterns = ignored_patterns
 29 |         self.ls_timeout = ls_timeout
 30 |         self.ls_specific_settings = ls_specific_settings
 31 |         self.trace_lsp_communication = trace_lsp_communication
 32 | 
 33 |     def create_language_server(self, language: Language) -> SolidLanguageServer:
 34 |         ls_config = LanguageServerConfig(
 35 |             code_language=language,
 36 |             ignored_paths=self.ignored_patterns,
 37 |             trace_lsp_communication=self.trace_lsp_communication,
 38 |             encoding=self.encoding,
 39 |         )
 40 | 
 41 |         log.info(f"Creating language server instance for {self.project_root}, language={language}.")
 42 |         return SolidLanguageServer.create(
 43 |             ls_config,
 44 |             self.project_root,
 45 |             timeout=self.ls_timeout,
 46 |             solidlsp_settings=SolidLSPSettings(
 47 |                 solidlsp_dir=SerenaPaths().serena_user_home_dir,
 48 |                 project_data_relative_path=SERENA_MANAGED_DIR_NAME,
 49 |                 ls_specific_settings=self.ls_specific_settings or {},
 50 |             ),
 51 |         )
 52 | 
 53 | 
 54 | class LanguageServerManager:
 55 |     """
 56 |     Manages one or more language servers for a project.
 57 |     """
 58 | 
 59 |     def __init__(
 60 |         self,
 61 |         language_servers: dict[Language, SolidLanguageServer],
 62 |         language_server_factory: LanguageServerFactory | None = None,
 63 |     ) -> None:
 64 |         """
 65 |         :param language_servers: a mapping from language to language server; the servers are assumed to be already started.
 66 |             The first server in the iteration order is used as the default server.
 67 |             All servers are assumed to serve the same project root.
 68 |         :param language_server_factory: factory for language server creation; if None, dynamic (re)creation of language servers
 69 |             is not supported
 70 |         """
 71 |         self._language_servers = language_servers
 72 |         self._language_server_factory = language_server_factory
 73 |         self._default_language_server = next(iter(language_servers.values()))
 74 |         self._root_path = self._default_language_server.repository_root_path
 75 | 
 76 |     @staticmethod
 77 |     def from_languages(languages: list[Language], factory: LanguageServerFactory) -> "LanguageServerManager":
 78 |         """
 79 |         Creates a manager with language servers for the given languages using the given factory.
 80 |         The language servers are started in parallel threads.
 81 | 
 82 |         :param languages: the languages for which to spawn language servers
 83 |         :param factory: the factory for language server creation
 84 |         :return: the instance
 85 |         """
 86 |         language_servers: dict[Language, SolidLanguageServer] = {}
 87 |         threads = []
 88 |         exceptions = {}
 89 |         lock = threading.Lock()
 90 | 
 91 |         def start_language_server(language: Language) -> None:
 92 |             try:
 93 |                 with LogTime(f"Language server startup (language={language.value})"):
 94 |                     language_server = factory.create_language_server(language)
 95 |                     language_server.start()
 96 |                     if not language_server.is_running():
 97 |                         raise RuntimeError(f"Failed to start the language server for language {language.value}")
 98 |                     with lock:
 99 |                         language_servers[language] = language_server
100 |             except Exception as e:
101 |                 log.error(f"Error starting language server for language {language.value}: {e}", exc_info=e)
102 |                 with lock:
103 |                     exceptions[language] = e
104 | 
105 |         # start language servers in parallel threads
106 |         for language in languages:
107 |             thread = threading.Thread(target=start_language_server, args=(language,), name="StartLS:" + language.value)
108 |             thread.start()
109 |             threads.append(thread)
110 |         for thread in threads:
111 |             thread.join()
112 | 
113 |         # If any server failed to start up, raise an exception and stop all started language servers
114 |         if exceptions:
115 |             for ls in language_servers.values():
116 |                 ls.stop()
117 |             failure_messages = "\n".join([f"{lang.value}: {e}" for lang, e in exceptions.items()])
118 |             raise Exception(f"Failed to start language servers:\n{failure_messages}")
119 | 
120 |         return LanguageServerManager(language_servers, factory)
121 | 
122 |     def get_root_path(self) -> str:
123 |         return self._root_path
124 | 
125 |     def _ensure_functional_ls(self, ls: SolidLanguageServer) -> SolidLanguageServer:
126 |         if not ls.is_running():
127 |             log.warning(f"Language server for language {ls.language} is not running; restarting ...")
128 |             ls = self.restart_language_server(ls.language)
129 |         return ls
130 | 
131 |     def get_language_server(self, relative_path: str) -> SolidLanguageServer:
132 |         ls: SolidLanguageServer | None = None
133 |         if len(self._language_servers) > 1:
134 |             for candidate in self._language_servers.values():
135 |                 if not candidate.is_ignored_path(relative_path, ignore_unsupported_files=True):
136 |                     ls = candidate
137 |                     break
138 |         if ls is None:
139 |             ls = self._default_language_server
140 |         return self._ensure_functional_ls(ls)
141 | 
142 |     def _create_and_start_language_server(self, language: Language) -> SolidLanguageServer:
143 |         if self._language_server_factory is None:
144 |             raise ValueError(f"No language server factory available to create language server for {language}")
145 |         language_server = self._language_server_factory.create_language_server(language)
146 |         language_server.start()
147 |         self._language_servers[language] = language_server
148 |         return language_server
149 | 
150 |     def restart_language_server(self, language: Language) -> SolidLanguageServer:
151 |         """
152 |         Forces recreation and restart of the language server for the given language.
153 |         It is assumed that the language server for the given language is no longer running.
154 | 
155 |         :param language: the language
156 |         :return: the newly created language server
157 |         """
158 |         if language not in self._language_servers:
159 |             raise ValueError(f"No language server for language {language.value} present; cannot restart")
160 |         return self._create_and_start_language_server(language)
161 | 
162 |     def add_language_server(self, language: Language) -> SolidLanguageServer:
163 |         """
164 |         Dynamically adds a new language server for the given language.
165 | 
166 |         :param language: the language
167 |         :param factory: the factory to create the language server
168 |         :return: the newly created language server
169 |         """
170 |         if language in self._language_servers:
171 |             raise ValueError(f"Language server for language {language.value} already present")
172 |         return self._create_and_start_language_server(language)
173 | 
174 |     def remove_language_server(self, language: Language, save_cache: bool = False) -> None:
175 |         """
176 |         Removes the language server for the given language, stopping it if it is running.
177 | 
178 |         :param language: the language
179 |         """
180 |         if language not in self._language_servers:
181 |             raise ValueError(f"No language server for language {language.value} present; cannot remove")
182 |         ls = self._language_servers.pop(language)
183 |         self._stop_language_server(ls, save_cache=save_cache)
184 | 
185 |     def get_active_languages(self) -> list[Language]:
186 |         """
187 |         Returns the list of languages for which language servers are currently managed.
188 | 
189 |         :return: list of languages
190 |         """
191 |         return list(self._language_servers.keys())
192 | 
193 |     @staticmethod
194 |     def _stop_language_server(ls: SolidLanguageServer, save_cache: bool = False, timeout: float = 2.0) -> None:
195 |         if ls.is_running():
196 |             if save_cache:
197 |                 ls.save_cache()
198 |             log.info(f"Stopping language server for language {ls.language} ...")
199 |             ls.stop(shutdown_timeout=timeout)
200 | 
201 |     def iter_language_servers(self) -> Iterator[SolidLanguageServer]:
202 |         for ls in self._language_servers.values():
203 |             yield self._ensure_functional_ls(ls)
204 | 
205 |     def stop_all(self, save_cache: bool = False, timeout: float = 2.0) -> None:
206 |         """
207 |         Stops all managed language servers.
208 | 
209 |         :param save_cache: whether to save the cache before stopping
210 |         :param timeout: timeout for shutdown of each language server
211 |         """
212 |         for ls in self.iter_language_servers():
213 |             self._stop_language_server(ls, save_cache=save_cache, timeout=timeout)
214 | 
215 |     def save_all_caches(self) -> None:
216 |         """
217 |         Saves the caches of all managed language servers.
218 |         """
219 |         for ls in self.iter_language_servers():
220 |             if ls.is_running():
221 |                 ls.save_cache()
222 | 
```

--------------------------------------------------------------------------------
/test/serena/test_mcp.py:
--------------------------------------------------------------------------------

```python
  1 | """Tests for the mcp.py module in serena."""
  2 | 
  3 | import pytest
  4 | from mcp.server.fastmcp.tools.base import Tool as MCPTool
  5 | 
  6 | from serena.agent import Tool, ToolRegistry
  7 | from serena.config.context_mode import SerenaAgentContext
  8 | from serena.mcp import SerenaMCPFactory
  9 | 
 10 | make_tool = SerenaMCPFactory.make_mcp_tool
 11 | 
 12 | 
 13 | # Create a mock agent for tool initialization
 14 | class MockAgent:
 15 |     def __init__(self):
 16 |         self.project_config = None
 17 |         self.serena_config = None
 18 | 
 19 |     @staticmethod
 20 |     def get_context() -> SerenaAgentContext:
 21 |         return SerenaAgentContext.load_default()
 22 | 
 23 | 
 24 | class BaseMockTool(Tool):
 25 |     """A mock Tool class for testing."""
 26 | 
 27 |     def __init__(self):
 28 |         super().__init__(MockAgent())  # type: ignore
 29 | 
 30 | 
 31 | class BasicTool(BaseMockTool):
 32 |     """A mock Tool class for testing."""
 33 | 
 34 |     def apply(self, name: str, age: int = 0) -> str:
 35 |         """This is a test function.
 36 | 
 37 |         :param name: The person's name
 38 |         :param age: The person's age
 39 |         :return: A greeting message
 40 |         """
 41 |         return f"Hello {name}, you are {age} years old!"
 42 | 
 43 |     def apply_ex(
 44 |         self,
 45 |         log_call: bool = True,
 46 |         catch_exceptions: bool = True,
 47 |         **kwargs,
 48 |     ) -> str:
 49 |         """Mock implementation of apply_ex."""
 50 |         return self.apply(**kwargs)
 51 | 
 52 | 
 53 | def test_make_tool_basic() -> None:
 54 |     """Test that make_tool correctly creates an MCP tool from a Tool object."""
 55 |     mock_tool = BasicTool()
 56 | 
 57 |     mcp_tool = make_tool(mock_tool)
 58 | 
 59 |     # Test that the MCP tool has the correct properties
 60 |     assert isinstance(mcp_tool, MCPTool)
 61 |     assert mcp_tool.name == "basic"
 62 |     assert "This is a test function. Returns A greeting message." in mcp_tool.description
 63 | 
 64 |     # Test that the parameters were correctly processed
 65 |     parameters = mcp_tool.parameters
 66 |     assert "properties" in parameters
 67 |     assert "name" in parameters["properties"]
 68 |     assert "age" in parameters["properties"]
 69 |     assert parameters["properties"]["name"]["description"] == "The person's name."
 70 |     assert parameters["properties"]["age"]["description"] == "The person's age."
 71 | 
 72 | 
 73 | def test_make_tool_execution() -> None:
 74 |     """Test that the execution function created by make_tool works correctly."""
 75 |     mock_tool = BasicTool()
 76 |     mcp_tool = make_tool(mock_tool)
 77 | 
 78 |     # Execute the MCP tool function
 79 |     result = mcp_tool.fn(name="Alice", age=30)
 80 | 
 81 |     assert result == "Hello Alice, you are 30 years old!"
 82 | 
 83 | 
 84 | def test_make_tool_no_params() -> None:
 85 |     """Test make_tool with a function that has no parameters."""
 86 | 
 87 |     class NoParamsTool(BaseMockTool):
 88 |         def apply(self) -> str:
 89 |             """This is a test function with no parameters.
 90 | 
 91 |             :return: A simple result
 92 |             """
 93 |             return "Simple result"
 94 | 
 95 |         def apply_ex(self, *args, **kwargs) -> str:
 96 |             return self.apply()
 97 | 
 98 |     tool = NoParamsTool()
 99 |     mcp_tool = make_tool(tool)
100 | 
101 |     assert mcp_tool.name == "no_params"
102 |     assert "This is a test function with no parameters. Returns A simple result." in mcp_tool.description
103 |     assert mcp_tool.parameters["properties"] == {}
104 | 
105 | 
106 | def test_make_tool_no_return_description() -> None:
107 |     """Test make_tool with a function that has no return description."""
108 | 
109 |     class NoReturnTool(BaseMockTool):
110 |         def apply(self, param: str) -> str:
111 |             """This is a test function.
112 | 
113 |             :param param: The parameter
114 |             """
115 |             return f"Processed: {param}"
116 | 
117 |         def apply_ex(self, *args, **kwargs) -> str:
118 |             return self.apply(**kwargs)
119 | 
120 |     tool = NoReturnTool()
121 |     mcp_tool = make_tool(tool)
122 | 
123 |     assert mcp_tool.name == "no_return"
124 |     assert mcp_tool.description == "This is a test function."
125 |     assert mcp_tool.parameters["properties"]["param"]["description"] == "The parameter."
126 | 
127 | 
128 | def test_make_tool_parameter_not_in_docstring() -> None:
129 |     """Test make_tool when a parameter in properties is not in the docstring."""
130 | 
131 |     class MissingParamTool(BaseMockTool):
132 |         def apply(self, name: str, missing_param: str = "") -> str:
133 |             """This is a test function.
134 | 
135 |             :param name: The person's name
136 |             """
137 |             return f"Hello {name}! Missing param: {missing_param}"
138 | 
139 |         def apply_ex(self, *args, **kwargs) -> str:
140 |             return self.apply(**kwargs)
141 | 
142 |     tool = MissingParamTool()
143 |     mcp_tool = make_tool(tool)
144 | 
145 |     assert "name" in mcp_tool.parameters["properties"]
146 |     assert "missing_param" in mcp_tool.parameters["properties"]
147 |     assert mcp_tool.parameters["properties"]["name"]["description"] == "The person's name."
148 |     assert "description" not in mcp_tool.parameters["properties"]["missing_param"]
149 | 
150 | 
151 | def test_make_tool_multiline_docstring() -> None:
152 |     """Test make_tool with a complex multi-line docstring."""
153 | 
154 |     class ComplexDocTool(BaseMockTool):
155 |         def apply(self, project_file_path: str, host: str, port: int) -> str:
156 |             """Create an MCP server.
157 | 
158 |             This function creates and configures a Model Context Protocol server
159 |             with the specified settings.
160 | 
161 |             :param project_file_path: The path to the project file, or None
162 |             :param host: The host to bind to
163 |             :param port: The port to bind to
164 |             :return: A configured FastMCP server instance
165 |             """
166 |             return f"Server config: {project_file_path}, {host}:{port}"
167 | 
168 |         def apply_ex(self, *args, **kwargs) -> str:
169 |             return self.apply(**kwargs)
170 | 
171 |     tool = ComplexDocTool()
172 |     mcp_tool = make_tool(tool)
173 | 
174 |     assert "Create an MCP server" in mcp_tool.description
175 |     assert "Returns A configured FastMCP server instance" in mcp_tool.description
176 |     assert mcp_tool.parameters["properties"]["project_file_path"]["description"] == "The path to the project file, or None."
177 |     assert mcp_tool.parameters["properties"]["host"]["description"] == "The host to bind to."
178 |     assert mcp_tool.parameters["properties"]["port"]["description"] == "The port to bind to."
179 | 
180 | 
181 | def test_make_tool_capitalization_and_periods() -> None:
182 |     """Test that make_tool properly handles capitalization and periods in descriptions."""
183 | 
184 |     class FormatTool(BaseMockTool):
185 |         def apply(self, param1: str, param2: str, param3: str) -> str:
186 |             """Test function.
187 | 
188 |             :param param1: lowercase description
189 |             :param param2: description with period.
190 |             :param param3: description with Capitalized word.
191 |             """
192 |             return f"Formatted: {param1}, {param2}, {param3}"
193 | 
194 |         def apply_ex(self, *args, **kwargs) -> str:
195 |             return self.apply(**kwargs)
196 | 
197 |     tool = FormatTool()
198 |     mcp_tool = make_tool(tool)
199 | 
200 |     assert mcp_tool.parameters["properties"]["param1"]["description"] == "Lowercase description."
201 |     assert mcp_tool.parameters["properties"]["param2"]["description"] == "Description with period."
202 |     assert mcp_tool.parameters["properties"]["param3"]["description"] == "Description with Capitalized word."
203 | 
204 | 
205 | def test_make_tool_missing_apply() -> None:
206 |     """Test make_tool with a tool that doesn't have an apply method."""
207 | 
208 |     class BadTool(BaseMockTool):
209 |         pass
210 | 
211 |     tool = BadTool()
212 | 
213 |     with pytest.raises(AttributeError):
214 |         make_tool(tool)
215 | 
216 | 
217 | @pytest.mark.parametrize(
218 |     "docstring, expected_description",
219 |     [
220 |         (
221 |             """This is a test function.
222 | 
223 |             :param param: The parameter
224 |             :return: A result
225 |             """,
226 |             "This is a test function. Returns A result.",
227 |         ),
228 |         (
229 |             """
230 |             :param param: The parameter
231 |             :return: A result
232 |             """,
233 |             "Returns A result.",
234 |         ),
235 |         (
236 |             """
237 |             :param param: The parameter
238 |             """,
239 |             "",
240 |         ),
241 |         ("Description without params.", "Description without params."),
242 |     ],
243 | )
244 | def test_make_tool_descriptions(docstring, expected_description) -> None:
245 |     """Test make_tool with various docstring formats."""
246 | 
247 |     class TestTool(BaseMockTool):
248 |         def apply(self, param: str) -> str:
249 |             return f"Result: {param}"
250 | 
251 |         def apply_ex(self, *args, **kwargs) -> str:
252 |             return self.apply(**kwargs)
253 | 
254 |     # Dynamically set the docstring
255 |     TestTool.apply.__doc__ = docstring
256 | 
257 |     tool = TestTool()
258 |     mcp_tool = make_tool(tool)
259 | 
260 |     assert mcp_tool.name == "test"
261 |     assert mcp_tool.description == expected_description
262 | 
263 | 
264 | def is_test_mock_class(tool_class: type) -> bool:
265 |     """Check if a class is a test mock class."""
266 |     # Check if the class is defined in a test module
267 |     module_name = tool_class.__module__
268 |     return (
269 |         module_name.startswith(("test.", "tests."))
270 |         or "test_" in module_name
271 |         or tool_class.__name__
272 |         in [
273 |             "BaseMockTool",
274 |             "BasicTool",
275 |             "BadTool",
276 |             "NoParamsTool",
277 |             "NoReturnTool",
278 |             "MissingParamTool",
279 |             "ComplexDocTool",
280 |             "FormatTool",
281 |             "NoDescriptionTool",
282 |         ]
283 |     )
284 | 
285 | 
286 | @pytest.mark.parametrize("tool_class", ToolRegistry().get_all_tool_classes())
287 | def test_make_tool_all_tools(tool_class) -> None:
288 |     """Test that make_tool works for all tools in the codebase."""
289 |     # Create an instance of the tool
290 |     tool_instance = tool_class(MockAgent())
291 | 
292 |     # Try to create an MCP tool from it
293 |     mcp_tool = make_tool(tool_instance)
294 | 
295 |     # Basic validation
296 |     assert isinstance(mcp_tool, MCPTool)
297 |     assert mcp_tool.name == tool_class.get_name_from_cls()
298 | 
299 |     # The description should be a string (either from docstring or default)
300 |     assert isinstance(mcp_tool.description, str)
301 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/bash_language_server.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Provides Bash specific instantiation of the LanguageServer class using bash-language-server.
  3 | Contains various configurations and settings specific to Bash scripting.
  4 | """
  5 | 
  6 | import logging
  7 | import os
  8 | import pathlib
  9 | import shutil
 10 | import threading
 11 | 
 12 | from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
 13 | from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
 14 | from solidlsp.ls_config import LanguageServerConfig
 15 | from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
 16 | from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
 17 | from solidlsp.settings import SolidLSPSettings
 18 | 
 19 | log = logging.getLogger(__name__)
 20 | 
 21 | 
 22 | class BashLanguageServer(SolidLanguageServer):
 23 |     """
 24 |     Provides Bash specific instantiation of the LanguageServer class using bash-language-server.
 25 |     Contains various configurations and settings specific to Bash scripting.
 26 |     """
 27 | 
 28 |     def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
 29 |         """
 30 |         Creates a BashLanguageServer instance. This class is not meant to be instantiated directly.
 31 |         Use LanguageServer.create() instead.
 32 |         """
 33 |         bash_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
 34 |         super().__init__(
 35 |             config,
 36 |             repository_root_path,
 37 |             ProcessLaunchInfo(cmd=bash_lsp_executable_path, cwd=repository_root_path),
 38 |             "bash",
 39 |             solidlsp_settings,
 40 |         )
 41 |         self.server_ready = threading.Event()
 42 |         self.initialize_searcher_command_available = threading.Event()
 43 | 
 44 |     @classmethod
 45 |     def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
 46 |         """
 47 |         Setup runtime dependencies for Bash Language Server and return the command to start the server.
 48 |         """
 49 |         # Verify both node and npm are installed
 50 |         is_node_installed = shutil.which("node") is not None
 51 |         assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
 52 |         is_npm_installed = shutil.which("npm") is not None
 53 |         assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
 54 | 
 55 |         deps = RuntimeDependencyCollection(
 56 |             [
 57 |                 RuntimeDependency(
 58 |                     id="bash-language-server",
 59 |                     description="bash-language-server package",
 60 |                     command="npm install --prefix ./ [email protected]",
 61 |                     platform_id="any",
 62 |                 ),
 63 |             ]
 64 |         )
 65 | 
 66 |         # Install bash-language-server if not already installed
 67 |         bash_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "bash-lsp")
 68 |         bash_executable_path = os.path.join(bash_ls_dir, "node_modules", ".bin", "bash-language-server")
 69 | 
 70 |         # Handle Windows executable extension
 71 |         if os.name == "nt":
 72 |             bash_executable_path += ".cmd"
 73 | 
 74 |         if not os.path.exists(bash_executable_path):
 75 |             log.info(f"Bash Language Server executable not found at {bash_executable_path}. Installing...")
 76 |             deps.install(bash_ls_dir)
 77 |             log.info("Bash language server dependencies installed successfully")
 78 | 
 79 |         if not os.path.exists(bash_executable_path):
 80 |             raise FileNotFoundError(
 81 |                 f"bash-language-server executable not found at {bash_executable_path}, something went wrong with the installation."
 82 |             )
 83 |         return f"{bash_executable_path} start"
 84 | 
 85 |     @staticmethod
 86 |     def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
 87 |         """
 88 |         Returns the initialize params for the Bash Language Server.
 89 |         """
 90 |         root_uri = pathlib.Path(repository_absolute_path).as_uri()
 91 |         initialize_params = {
 92 |             "locale": "en",
 93 |             "capabilities": {
 94 |                 "textDocument": {
 95 |                     "synchronization": {"didSave": True, "dynamicRegistration": True},
 96 |                     "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
 97 |                     "definition": {"dynamicRegistration": True},
 98 |                     "references": {"dynamicRegistration": True},
 99 |                     "documentSymbol": {
100 |                         "dynamicRegistration": True,
101 |                         "hierarchicalDocumentSymbolSupport": True,
102 |                         "symbolKind": {"valueSet": list(range(1, 27))},
103 |                     },
104 |                     "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
105 |                     "signatureHelp": {"dynamicRegistration": True},
106 |                     "codeAction": {"dynamicRegistration": True},
107 |                 },
108 |                 "workspace": {
109 |                     "workspaceFolders": True,
110 |                     "didChangeConfiguration": {"dynamicRegistration": True},
111 |                     "symbol": {"dynamicRegistration": True},
112 |                 },
113 |             },
114 |             "processId": os.getpid(),
115 |             "rootPath": repository_absolute_path,
116 |             "rootUri": root_uri,
117 |             "workspaceFolders": [
118 |                 {
119 |                     "uri": root_uri,
120 |                     "name": os.path.basename(repository_absolute_path),
121 |                 }
122 |             ],
123 |         }
124 |         return initialize_params  # type: ignore
125 | 
126 |     def _start_server(self) -> None:
127 |         """
128 |         Starts the Bash Language Server, waits for the server to be ready and yields the LanguageServer instance.
129 |         """
130 | 
131 |         def register_capability_handler(params: dict) -> None:
132 |             assert "registrations" in params
133 |             for registration in params["registrations"]:
134 |                 if registration["method"] == "workspace/executeCommand":
135 |                     self.initialize_searcher_command_available.set()
136 |             return
137 | 
138 |         def execute_client_command_handler(params: dict) -> list:
139 |             return []
140 | 
141 |         def do_nothing(params: dict) -> None:
142 |             return
143 | 
144 |         def window_log_message(msg: dict) -> None:
145 |             log.info(f"LSP: window/logMessage: {msg}")
146 |             # Check for bash-language-server ready signals
147 |             message_text = msg.get("message", "")
148 |             if "Analyzing" in message_text or "analysis complete" in message_text.lower():
149 |                 log.info("Bash language server analysis signals detected")
150 |                 self.server_ready.set()
151 |                 self.completions_available.set()
152 | 
153 |         self.server.on_request("client/registerCapability", register_capability_handler)
154 |         self.server.on_notification("window/logMessage", window_log_message)
155 |         self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
156 |         self.server.on_notification("$/progress", do_nothing)
157 |         self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
158 | 
159 |         log.info("Starting Bash server process")
160 |         self.server.start()
161 |         initialize_params = self._get_initialize_params(self.repository_root_path)
162 | 
163 |         log.info("Sending initialize request from LSP client to LSP server and awaiting response")
164 |         init_response = self.server.send.initialize(initialize_params)
165 |         log.debug(f"Received initialize response from bash server: {init_response}")
166 | 
167 |         # Enhanced capability checks for bash-language-server 5.6.0
168 |         assert init_response["capabilities"]["textDocumentSync"] in [1, 2]  # Full or Incremental
169 |         assert "completionProvider" in init_response["capabilities"]
170 | 
171 |         # Verify document symbol support is available
172 |         if "documentSymbolProvider" in init_response["capabilities"]:
173 |             log.info("Bash server supports document symbols")
174 |         else:
175 |             log.warning("Warning: Bash server does not report document symbol support")
176 | 
177 |         self.server.notify.initialized({})
178 | 
179 |         # Wait for server readiness with timeout
180 |         log.info("Waiting for Bash language server to be ready...")
181 |         if not self.server_ready.wait(timeout=3.0):
182 |             # Fallback: assume server is ready after timeout
183 |             # This is common. bash-language-server doesn't always send explicit ready signals. Log as info
184 |             log.info("Timeout waiting for bash server ready signal, proceeding anyway")
185 |             self.server_ready.set()
186 |             self.completions_available.set()
187 |         else:
188 |             log.info("Bash server initialization complete")
189 | 
190 |     def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols:
191 |         # Uses the standard LSP documentSymbol request which provides reliable function detection
192 |         # for all bash function syntaxes including:
193 |         # - function name() { ... } (with function keyword)
194 |         # - name() { ... } (traditional syntax)
195 |         # - Functions with various indentation levels
196 |         # - Functions with comments before/after/inside
197 | 
198 |         log.debug(f"Requesting document symbols via LSP for {relative_file_path}")
199 | 
200 |         # Use the standard LSP approach - bash-language-server handles all function syntaxes correctly
201 |         document_symbols = super().request_document_symbols(relative_file_path, file_buffer=file_buffer)
202 | 
203 |         # Log detection results for debugging
204 |         functions = [s for s in document_symbols.iter_symbols() if s.get("kind") == 12]
205 |         log.info(f"LSP function detection for {relative_file_path}: Found {len(functions)} functions")
206 | 
207 |         return document_symbols
208 | 
```

--------------------------------------------------------------------------------
/src/serena/task_executor.py:
--------------------------------------------------------------------------------

```python
  1 | import concurrent.futures
  2 | import threading
  3 | import time
  4 | from collections.abc import Callable
  5 | from concurrent.futures import Future
  6 | from dataclasses import dataclass
  7 | from threading import Thread
  8 | from typing import Generic, TypeVar
  9 | 
 10 | from sensai.util import logging
 11 | from sensai.util.logging import LogTime
 12 | from sensai.util.string import ToStringMixin
 13 | 
 14 | log = logging.getLogger(__name__)
 15 | T = TypeVar("T")
 16 | 
 17 | 
 18 | class TaskExecutor:
 19 |     def __init__(self, name: str):
 20 |         self._task_executor_lock = threading.Lock()
 21 |         self._task_executor_queue: list[TaskExecutor.Task] = []
 22 |         self._task_executor_thread = Thread(target=self._process_task_queue, name=name, daemon=True)
 23 |         self._task_executor_thread.start()
 24 |         self._task_executor_task_index = 1
 25 |         self._task_executor_current_task: TaskExecutor.Task | None = None
 26 |         self._task_executor_last_executed_task_info: TaskExecutor.TaskInfo | None = None
 27 | 
 28 |     class Task(ToStringMixin, Generic[T]):
 29 |         def __init__(self, function: Callable[[], T], name: str, logged: bool = True, timeout: float | None = None):
 30 |             """
 31 |             :param function: the function representing the task to execute
 32 |             :param name: the name of the task
 33 |             :param logged: whether to log management of the task; if False, only errors will be logged
 34 |             :param timeout: the maximum time to wait for task completion in seconds, or None to wait indefinitely
 35 |             """
 36 |             self.name = name
 37 |             self.future: concurrent.futures.Future = concurrent.futures.Future()
 38 |             self.logged = logged
 39 |             self.timeout = timeout
 40 |             self._function = function
 41 | 
 42 |         def _tostring_includes(self) -> list[str]:
 43 |             return ["name"]
 44 | 
 45 |         def start(self) -> None:
 46 |             """
 47 |             Executes the task in a separate thread, setting the result or exception on the future.
 48 |             """
 49 | 
 50 |             def run_task() -> None:
 51 |                 try:
 52 |                     if self.future.done():
 53 |                         if self.logged:
 54 |                             log.info(f"Task {self.name} was already completed/cancelled; skipping execution")
 55 |                         return
 56 |                     with LogTime(self.name, logger=log, enabled=self.logged):
 57 |                         result = self._function()
 58 |                         if not self.future.done():
 59 |                             self.future.set_result(result)
 60 |                 except Exception as e:
 61 |                     if not self.future.done():
 62 |                         log.error(f"Error during execution of {self.name}: {e}", exc_info=e)
 63 |                         self.future.set_exception(e)
 64 | 
 65 |             thread = Thread(target=run_task, name=self.name)
 66 |             thread.start()
 67 | 
 68 |         def is_done(self) -> bool:
 69 |             """
 70 |             :return: whether the task has completed (either successfully, with failure, or via cancellation)
 71 |             """
 72 |             return self.future.done()
 73 | 
 74 |         def result(self, timeout: float | None = None) -> T:
 75 |             """
 76 |             Blocks until the task is done or the timeout is reached, and returns the result.
 77 |             If an exception occurred during task execution, it is raised here.
 78 |             If the timeout is reached, a TimeoutError is raised (but the task is not cancelled).
 79 |             If the task is cancelled, a CancelledError is raised.
 80 | 
 81 |             :param timeout: the maximum time to wait in seconds; if None, use the task's own timeout
 82 |                 (which may be None to wait indefinitely)
 83 |             :return: True if the task is done, False if the timeout was reached
 84 |             """
 85 |             return self.future.result(timeout=timeout)
 86 | 
 87 |         def cancel(self) -> None:
 88 |             """
 89 |             Cancels the task. If it has not yet started, it will not be executed.
 90 |             If it has already started, its future will be marked as cancelled and will raise a CancelledError
 91 |             when its result is requested.
 92 |             """
 93 |             self.future.cancel()
 94 | 
 95 |         def wait_until_done(self, timeout: float | None = None) -> None:
 96 |             """
 97 |             Waits until the task is done or the timeout is reached.
 98 |             The task is done if it either completed successfully, failed with an exception, or was cancelled.
 99 | 
100 |             :param timeout: the maximum time to wait in seconds; if None, use the task's own timeout
101 |                 (which may be None to wait indefinitely)
102 |             """
103 |             try:
104 |                 self.future.result(timeout=timeout)
105 |             except:
106 |                 pass
107 | 
108 |     def _process_task_queue(self) -> None:
109 |         while True:
110 |             # obtain task from the queue
111 |             task: TaskExecutor.Task | None = None
112 |             with self._task_executor_lock:
113 |                 if len(self._task_executor_queue) > 0:
114 |                     task = self._task_executor_queue.pop(0)
115 |             if task is None:
116 |                 time.sleep(0.1)
117 |                 continue
118 | 
119 |             # start task execution asynchronously
120 |             with self._task_executor_lock:
121 |                 self._task_executor_current_task = task
122 |             if task.logged:
123 |                 log.info("Starting execution of %s", task.name)
124 |             task.start()
125 | 
126 |             # wait for task completion
127 |             task.wait_until_done(timeout=task.timeout)
128 |             with self._task_executor_lock:
129 |                 self._task_executor_current_task = None
130 |                 if task.logged:
131 |                     self._task_executor_last_executed_task_info = self.TaskInfo.from_task(task, is_running=False)
132 | 
133 |     @dataclass
134 |     class TaskInfo:
135 |         name: str
136 |         is_running: bool
137 |         future: Future
138 |         """
139 |         future for accessing the task's result
140 |         """
141 |         task_id: int
142 |         """
143 |         unique identifier of the task
144 |         """
145 |         logged: bool
146 | 
147 |         def finished_successfully(self) -> bool:
148 |             return self.future.done() and not self.future.cancelled() and self.future.exception() is None
149 | 
150 |         @staticmethod
151 |         def from_task(task: "TaskExecutor.Task", is_running: bool) -> "TaskExecutor.TaskInfo":
152 |             return TaskExecutor.TaskInfo(name=task.name, is_running=is_running, future=task.future, task_id=id(task), logged=task.logged)
153 | 
154 |         def cancel(self) -> None:
155 |             self.future.cancel()
156 | 
157 |     def get_current_tasks(self) -> list[TaskInfo]:
158 |         """
159 |         Gets the list of tasks currently running or queued for execution.
160 |         The function returns a list of thread-safe TaskInfo objects (specifically created for the caller).
161 | 
162 |         :return: the list of tasks in the execution order (running task first)
163 |         """
164 |         tasks = []
165 |         with self._task_executor_lock:
166 |             if self._task_executor_current_task is not None:
167 |                 tasks.append(self.TaskInfo.from_task(self._task_executor_current_task, True))
168 |             for task in self._task_executor_queue:
169 |                 if not task.is_done():
170 |                     tasks.append(self.TaskInfo.from_task(task, False))
171 |         return tasks
172 | 
173 |     def issue_task(self, task: Callable[[], T], name: str | None = None, logged: bool = True, timeout: float | None = None) -> Task[T]:
174 |         """
175 |         Issue a task to the executor for asynchronous execution.
176 |         It is ensured that tasks are executed in the order they are issued, one after another.
177 | 
178 |         :param task: the task to execute
179 |         :param name: the name of the task for logging purposes; if None, use the task function's name
180 |         :param logged: whether to log management of the task; if False, only errors will be logged
181 |         :param timeout: the maximum time to wait for task completion in seconds, or None to wait indefinitely
182 |         :return: the task object, through which the task's future result can be accessed
183 |         """
184 |         with self._task_executor_lock:
185 |             if logged:
186 |                 task_prefix_name = f"Task-{self._task_executor_task_index}"
187 |                 self._task_executor_task_index += 1
188 |             else:
189 |                 task_prefix_name = "BackgroundTask"
190 |             task_name = f"{task_prefix_name}:{name or task.__name__}"
191 |             if logged:
192 |                 log.info(f"Scheduling {task_name}")
193 |             task_obj = self.Task(function=task, name=task_name, logged=logged, timeout=timeout)
194 |             self._task_executor_queue.append(task_obj)
195 |             return task_obj
196 | 
197 |     def execute_task(self, task: Callable[[], T], name: str | None = None, logged: bool = True, timeout: float | None = None) -> T:
198 |         """
199 |         Executes the given task synchronously via the agent's task executor.
200 |         This is useful for tasks that need to be executed immediately and whose results are needed right away.
201 | 
202 |         :param task: the task to execute
203 |         :param name: the name of the task for logging purposes; if None, use the task function's name
204 |         :param logged: whether to log management of the task; if False, only errors will be logged
205 |         :param timeout: the maximum time to wait for task completion in seconds, or None to wait indefinitely
206 |         :return: the result of the task execution
207 |         """
208 |         task_obj = self.issue_task(task, name=name, logged=logged, timeout=timeout)
209 |         return task_obj.result()
210 | 
211 |     def get_last_executed_task(self) -> TaskInfo | None:
212 |         """
213 |         Gets information about the last executed task.
214 | 
215 |         :return: TaskInfo of the last executed task, or None if no task has been executed yet.
216 |         """
217 |         with self._task_executor_lock:
218 |             return self._task_executor_last_executed_task_info
219 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/vts_language_server.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Language Server implementation for TypeScript/JavaScript using https://github.com/yioneko/vtsls,
  3 | which provides TypeScript language server functionality via VSCode's TypeScript extension
  4 | (contrary to typescript-language-server, which uses the TypeScript compiler directly).
  5 | """
  6 | 
  7 | import logging
  8 | import os
  9 | import pathlib
 10 | import shutil
 11 | import threading
 12 | from typing import cast
 13 | 
 14 | from overrides import override
 15 | 
 16 | from solidlsp.ls import SolidLanguageServer
 17 | from solidlsp.ls_config import LanguageServerConfig
 18 | from solidlsp.ls_utils import PlatformId, PlatformUtils
 19 | from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
 20 | from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
 21 | from solidlsp.settings import SolidLSPSettings
 22 | 
 23 | from .common import RuntimeDependency, RuntimeDependencyCollection
 24 | 
 25 | log = logging.getLogger(__name__)
 26 | 
 27 | 
 28 | class VtsLanguageServer(SolidLanguageServer):
 29 |     """
 30 |     Provides TypeScript specific instantiation of the LanguageServer class using vtsls.
 31 |     Contains various configurations and settings specific to TypeScript via vtsls wrapper.
 32 |     """
 33 | 
 34 |     def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
 35 |         """
 36 |         Creates a VtsLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
 37 |         """
 38 |         vts_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
 39 |         super().__init__(
 40 |             config,
 41 |             repository_root_path,
 42 |             ProcessLaunchInfo(cmd=vts_lsp_executable_path, cwd=repository_root_path),
 43 |             "typescript",
 44 |             solidlsp_settings,
 45 |         )
 46 |         self.server_ready = threading.Event()
 47 |         self.initialize_searcher_command_available = threading.Event()
 48 | 
 49 |     @override
 50 |     def is_ignored_dirname(self, dirname: str) -> bool:
 51 |         return super().is_ignored_dirname(dirname) or dirname in [
 52 |             "node_modules",
 53 |             "dist",
 54 |             "build",
 55 |             "coverage",
 56 |         ]
 57 | 
 58 |     @classmethod
 59 |     def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
 60 |         """
 61 |         Setup runtime dependencies for VTS Language Server and return the command to start the server.
 62 |         """
 63 |         platform_id = PlatformUtils.get_platform_id()
 64 | 
 65 |         valid_platforms = [
 66 |             PlatformId.LINUX_x64,
 67 |             PlatformId.LINUX_arm64,
 68 |             PlatformId.OSX,
 69 |             PlatformId.OSX_x64,
 70 |             PlatformId.OSX_arm64,
 71 |             PlatformId.WIN_x64,
 72 |             PlatformId.WIN_arm64,
 73 |         ]
 74 |         assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for vtsls at the moment"
 75 | 
 76 |         deps = RuntimeDependencyCollection(
 77 |             [
 78 |                 RuntimeDependency(
 79 |                     id="vtsls",
 80 |                     description="vtsls language server package",
 81 |                     command="npm install --prefix ./ @vtsls/[email protected]",
 82 |                     platform_id="any",
 83 |                 ),
 84 |             ]
 85 |         )
 86 |         vts_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "vts-lsp")
 87 |         vts_executable_path = os.path.join(vts_ls_dir, "vtsls")
 88 | 
 89 |         # Verify both node and npm are installed
 90 |         is_node_installed = shutil.which("node") is not None
 91 |         assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
 92 |         is_npm_installed = shutil.which("npm") is not None
 93 |         assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
 94 | 
 95 |         # Install vtsls if not already installed
 96 |         if not os.path.exists(vts_ls_dir):
 97 |             os.makedirs(vts_ls_dir, exist_ok=True)
 98 |             deps.install(vts_ls_dir)
 99 | 
100 |         vts_executable_path = os.path.join(vts_ls_dir, "node_modules", ".bin", "vtsls")
101 | 
102 |         assert os.path.exists(vts_executable_path), "vtsls executable not found. Please install @vtsls/language-server and try again."
103 |         return f"{vts_executable_path} --stdio"
104 | 
105 |     @staticmethod
106 |     def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
107 |         """
108 |         Returns the initialize params for the VTS Language Server.
109 |         """
110 |         root_uri = pathlib.Path(repository_absolute_path).as_uri()
111 |         initialize_params = {
112 |             "locale": "en",
113 |             "capabilities": {
114 |                 "textDocument": {
115 |                     "synchronization": {"didSave": True, "dynamicRegistration": True},
116 |                     "definition": {"dynamicRegistration": True},
117 |                     "references": {"dynamicRegistration": True},
118 |                     "documentSymbol": {
119 |                         "dynamicRegistration": True,
120 |                         "hierarchicalDocumentSymbolSupport": True,
121 |                         "symbolKind": {"valueSet": list(range(1, 27))},
122 |                     },
123 |                     "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
124 |                     "signatureHelp": {"dynamicRegistration": True},
125 |                     "codeAction": {"dynamicRegistration": True},
126 |                 },
127 |                 "workspace": {
128 |                     "workspaceFolders": True,
129 |                     "didChangeConfiguration": {"dynamicRegistration": True},
130 |                     "symbol": {"dynamicRegistration": True},
131 |                     "configuration": True,  # This might be needed for vtsls
132 |                 },
133 |             },
134 |             "processId": os.getpid(),
135 |             "rootPath": repository_absolute_path,
136 |             "rootUri": root_uri,
137 |             "workspaceFolders": [
138 |                 {
139 |                     "uri": root_uri,
140 |                     "name": os.path.basename(repository_absolute_path),
141 |                 }
142 |             ],
143 |         }
144 |         return cast(InitializeParams, initialize_params)
145 | 
146 |     def _start_server(self) -> None:
147 |         """
148 |         Starts the VTS Language Server, waits for the server to be ready and yields the LanguageServer instance.
149 | 
150 |         Usage:
151 |         ```
152 |         async with lsp.start_server():
153 |             # LanguageServer has been initialized and ready to serve requests
154 |             await lsp.request_definition(...)
155 |             await lsp.request_references(...)
156 |             # Shutdown the LanguageServer on exit from scope
157 |         # LanguageServer has been shutdown
158 |         """
159 | 
160 |         def register_capability_handler(params: dict) -> None:
161 |             assert "registrations" in params
162 |             for registration in params["registrations"]:
163 |                 if registration["method"] == "workspace/executeCommand":
164 |                     self.initialize_searcher_command_available.set()
165 |             return
166 | 
167 |         def execute_client_command_handler(params: dict) -> list:
168 |             return []
169 | 
170 |         def workspace_configuration_handler(params: dict) -> list[dict] | dict:
171 |             # VTS may request workspace configuration
172 |             # Return empty configuration for each requested item
173 |             if "items" in params:
174 |                 return [{}] * len(params["items"])
175 |             return {}
176 | 
177 |         def do_nothing(params: dict) -> None:
178 |             return
179 | 
180 |         def window_log_message(msg: dict) -> None:
181 |             log.info(f"LSP: window/logMessage: {msg}")
182 | 
183 |         def check_experimental_status(params: dict) -> None:
184 |             """
185 |             Also listen for experimental/serverStatus as a backup signal
186 |             """
187 |             if params.get("quiescent") is True:
188 |                 self.server_ready.set()
189 |                 self.completions_available.set()
190 | 
191 |         self.server.on_request("client/registerCapability", register_capability_handler)
192 |         self.server.on_notification("window/logMessage", window_log_message)
193 |         self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
194 |         self.server.on_request("workspace/configuration", workspace_configuration_handler)
195 |         self.server.on_notification("$/progress", do_nothing)
196 |         self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
197 |         self.server.on_notification("experimental/serverStatus", check_experimental_status)
198 | 
199 |         log.info("Starting VTS server process")
200 |         self.server.start()
201 |         initialize_params = self._get_initialize_params(self.repository_root_path)
202 | 
203 |         log.info("Sending initialize request from LSP client to LSP server and awaiting response")
204 |         init_response = self.server.send.initialize(initialize_params)
205 | 
206 |         # VTS-specific capability checks
207 |         # Be more flexible with capabilities since vtsls might have different structure
208 |         log.debug(f"VTS init response capabilities: {init_response['capabilities']}")
209 | 
210 |         # Basic checks to ensure essential capabilities are present
211 |         assert "textDocumentSync" in init_response["capabilities"]
212 |         assert "completionProvider" in init_response["capabilities"]
213 | 
214 |         # Log the actual values for debugging
215 |         log.debug(f"textDocumentSync: {init_response['capabilities']['textDocumentSync']}")
216 |         log.debug(f"completionProvider: {init_response['capabilities']['completionProvider']}")
217 | 
218 |         self.server.notify.initialized({})
219 |         if self.server_ready.wait(timeout=1.0):
220 |             log.info("VTS server is ready")
221 |         else:
222 |             log.info("Timeout waiting for VTS server to become ready, proceeding anyway")
223 |             # Fallback: assume server is ready after timeout
224 |             self.server_ready.set()
225 |         self.completions_available.set()
226 | 
227 |     @override
228 |     def _get_wait_time_for_cross_file_referencing(self) -> float:
229 |         return 1
230 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/powershell/test_powershell_basic.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Basic integration tests for the PowerShell language server functionality.
  3 | 
  4 | These tests validate the functionality of the language server APIs
  5 | like request_document_symbols using the PowerShell test repository.
  6 | """
  7 | 
  8 | import pytest
  9 | 
 10 | from solidlsp import SolidLanguageServer
 11 | from solidlsp.ls_config import Language
 12 | 
 13 | 
 14 | @pytest.mark.powershell
 15 | class TestPowerShellLanguageServerBasics:
 16 |     """Test basic functionality of the PowerShell language server."""
 17 | 
 18 |     @pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
 19 |     def test_powershell_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
 20 |         """Test that PowerShell language server can be initialized successfully."""
 21 |         assert language_server is not None
 22 |         assert language_server.language == Language.POWERSHELL
 23 | 
 24 |     @pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
 25 |     def test_powershell_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
 26 |         """Test request_document_symbols for PowerShell files."""
 27 |         # Test getting symbols from main.ps1
 28 |         all_symbols, _root_symbols = language_server.request_document_symbols("main.ps1").get_all_symbols_and_roots()
 29 | 
 30 |         # Extract function symbols (LSP Symbol Kind 12)
 31 |         function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12]
 32 |         function_names = [symbol["name"] for symbol in function_symbols]
 33 | 
 34 |         # PSES returns function names in format "function FuncName ()" - check for function name substring
 35 |         def has_function(name: str) -> bool:
 36 |             return any(name in fn for fn in function_names)
 37 | 
 38 |         # Should detect the main functions from main.ps1
 39 |         assert has_function("Greet-User"), f"Should find Greet-User function in {function_names}"
 40 |         assert has_function("Process-Items"), f"Should find Process-Items function in {function_names}"
 41 |         assert has_function("Main"), f"Should find Main function in {function_names}"
 42 |         assert len(function_symbols) >= 3, f"Should find at least 3 functions, found {len(function_symbols)}"
 43 | 
 44 |     @pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
 45 |     def test_powershell_utils_functions(self, language_server: SolidLanguageServer) -> None:
 46 |         """Test function detection in utils.ps1 file."""
 47 |         # Test with utils.ps1
 48 |         utils_all_symbols, _utils_root_symbols = language_server.request_document_symbols("utils.ps1").get_all_symbols_and_roots()
 49 | 
 50 |         utils_function_symbols = [symbol for symbol in utils_all_symbols if symbol.get("kind") == 12]
 51 |         utils_function_names = [symbol["name"] for symbol in utils_function_symbols]
 52 | 
 53 |         # PSES returns function names in format "function FuncName ()" - check for function name substring
 54 |         def has_function(name: str) -> bool:
 55 |             return any(name in fn for fn in utils_function_names)
 56 | 
 57 |         # Should detect functions from utils.ps1
 58 |         expected_utils_functions = [
 59 |             "Convert-ToUpperCase",
 60 |             "Convert-ToLowerCase",
 61 |             "Remove-Whitespace",
 62 |             "Backup-File",
 63 |             "Test-ArrayContains",
 64 |             "Write-LogMessage",
 65 |             "Test-ValidEmail",
 66 |             "Test-IsNumber",
 67 |         ]
 68 | 
 69 |         for func_name in expected_utils_functions:
 70 |             assert has_function(func_name), f"Should find {func_name} function in utils.ps1, got {utils_function_names}"
 71 | 
 72 |         assert len(utils_function_symbols) >= 8, f"Should find at least 8 functions in utils.ps1, found {len(utils_function_symbols)}"
 73 | 
 74 |     @pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
 75 |     def test_powershell_function_with_parameters(self, language_server: SolidLanguageServer) -> None:
 76 |         """Test that functions with CmdletBinding and parameters are detected correctly."""
 77 |         all_symbols, _root_symbols = language_server.request_document_symbols("main.ps1").get_all_symbols_and_roots()
 78 | 
 79 |         function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12]
 80 | 
 81 |         # Find Greet-User function which has parameters
 82 |         # PSES returns function names in format "function FuncName ()"
 83 |         greet_user_symbol = next((sym for sym in function_symbols if "Greet-User" in sym["name"]), None)
 84 |         assert greet_user_symbol is not None, f"Should find Greet-User function in {[s['name'] for s in function_symbols]}"
 85 | 
 86 |         # Find Process-Items function which has array parameter
 87 |         process_items_symbol = next((sym for sym in function_symbols if "Process-Items" in sym["name"]), None)
 88 |         assert process_items_symbol is not None, f"Should find Process-Items function in {[s['name'] for s in function_symbols]}"
 89 | 
 90 |     @pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
 91 |     def test_powershell_all_function_detection(self, language_server: SolidLanguageServer) -> None:
 92 |         """Test that all expected functions are detected across both files."""
 93 |         # Get symbols from main.ps1
 94 |         main_all_symbols, _main_root_symbols = language_server.request_document_symbols("main.ps1").get_all_symbols_and_roots()
 95 |         main_functions = [symbol for symbol in main_all_symbols if symbol.get("kind") == 12]
 96 |         main_function_names = [func["name"] for func in main_functions]
 97 | 
 98 |         # Get symbols from utils.ps1
 99 |         utils_all_symbols, _utils_root_symbols = language_server.request_document_symbols("utils.ps1").get_all_symbols_and_roots()
100 |         utils_functions = [symbol for symbol in utils_all_symbols if symbol.get("kind") == 12]
101 |         utils_function_names = [func["name"] for func in utils_functions]
102 | 
103 |         # PSES returns function names in format "function FuncName ()" - check for function name substring
104 |         def has_main_function(name: str) -> bool:
105 |             return any(name in fn for fn in main_function_names)
106 | 
107 |         def has_utils_function(name: str) -> bool:
108 |             return any(name in fn for fn in utils_function_names)
109 | 
110 |         # Verify main.ps1 functions
111 |         expected_main = ["Greet-User", "Process-Items", "Main"]
112 |         for expected_func in expected_main:
113 |             assert has_main_function(expected_func), f"Should detect {expected_func} function in main.ps1, got {main_function_names}"
114 | 
115 |         # Verify utils.ps1 functions
116 |         expected_utils = [
117 |             "Convert-ToUpperCase",
118 |             "Convert-ToLowerCase",
119 |             "Remove-Whitespace",
120 |             "Backup-File",
121 |             "Test-ArrayContains",
122 |             "Write-LogMessage",
123 |             "Test-ValidEmail",
124 |             "Test-IsNumber",
125 |         ]
126 |         for expected_func in expected_utils:
127 |             assert has_utils_function(expected_func), f"Should detect {expected_func} function in utils.ps1, got {utils_function_names}"
128 | 
129 |         # Verify total counts
130 |         assert len(main_functions) >= 3, f"Should find at least 3 functions in main.ps1, found {len(main_functions)}"
131 |         assert len(utils_functions) >= 8, f"Should find at least 8 functions in utils.ps1, found {len(utils_functions)}"
132 | 
133 |     @pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
134 |     def test_powershell_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
135 |         """Test finding references to a function within the same file."""
136 |         main_path = "main.ps1"
137 | 
138 |         # Get symbols to find the Greet-User function which is called from Main
139 |         all_symbols, _root_symbols = language_server.request_document_symbols(main_path).get_all_symbols_and_roots()
140 | 
141 |         # Find Greet-User function definition
142 |         function_symbols = [s for s in all_symbols if s.get("kind") == 12]
143 |         greet_user_symbol = next((s for s in function_symbols if "Greet-User" in s["name"]), None)
144 |         assert greet_user_symbol is not None, f"Should find Greet-User function in {[s['name'] for s in function_symbols]}"
145 | 
146 |         # Find references to Greet-User (should be called from Main function at line 91)
147 |         sel_start = greet_user_symbol["selectionRange"]["start"]
148 |         refs = language_server.request_references(main_path, sel_start["line"], sel_start["character"])
149 | 
150 |         # Should find at least the call site in Main function
151 |         assert refs is not None and len(refs) >= 1, f"Should find references to Greet-User, got {refs}"
152 |         assert any(
153 |             "main.ps1" in ref.get("uri", ref.get("relativePath", "")) for ref in refs
154 |         ), f"Should find reference in main.ps1, got {refs}"
155 | 
156 |     @pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
157 |     def test_powershell_find_definition_across_files(self, language_server: SolidLanguageServer) -> None:
158 |         """Test finding definition of functions across files (main.ps1 -> utils.ps1)."""
159 |         # main.ps1 calls Convert-ToUpperCase from utils.ps1 at line 99 (0-indexed: 98)
160 |         # The call is: $upperName = Convert-ToUpperCase -InputString $User
161 |         # We'll request definition from the call site in main.ps1
162 |         main_path = "main.ps1"
163 | 
164 |         # Find definition of Convert-ToUpperCase from its usage in main.ps1
165 |         # Line 99 (1-indexed) = line 98 (0-indexed), character position ~16 for "Convert-ToUpperCase"
166 |         definition_locations = language_server.request_definition(main_path, 98, 18)
167 | 
168 |         # Should find the definition in utils.ps1
169 |         assert (
170 |             definition_locations is not None and len(definition_locations) >= 1
171 |         ), f"Should find definition of Convert-ToUpperCase, got {definition_locations}"
172 |         assert any(
173 |             "utils.ps1" in loc.get("uri", "") for loc in definition_locations
174 |         ), f"Should find definition in utils.ps1, got {definition_locations}"
175 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/clojure_lsp.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Provides Clojure specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Clojure.
  3 | """
  4 | 
  5 | import logging
  6 | import os
  7 | import pathlib
  8 | import shutil
  9 | import subprocess
 10 | import threading
 11 | from typing import cast
 12 | 
 13 | from solidlsp.ls import SolidLanguageServer
 14 | from solidlsp.ls_config import LanguageServerConfig
 15 | from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
 16 | from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
 17 | from solidlsp.settings import SolidLSPSettings
 18 | 
 19 | from .common import RuntimeDependency, RuntimeDependencyCollection
 20 | 
 21 | log = logging.getLogger(__name__)
 22 | 
 23 | 
 24 | def run_command(cmd: list, capture_output: bool = True) -> subprocess.CompletedProcess:
 25 |     return subprocess.run(
 26 |         cmd, stdout=subprocess.PIPE if capture_output else None, stderr=subprocess.STDOUT if capture_output else None, text=True, check=True
 27 |     )
 28 | 
 29 | 
 30 | def verify_clojure_cli() -> None:
 31 |     install_msg = "Please install the official Clojure CLI from:\n  https://clojure.org/guides/getting_started"
 32 |     if shutil.which("clojure") is None:
 33 |         raise FileNotFoundError("`clojure` not found.\n" + install_msg)
 34 | 
 35 |     help_proc = run_command(["clojure", "--help"])
 36 |     if "-Aaliases" not in help_proc.stdout:
 37 |         raise RuntimeError("Detected a Clojure executable, but it does not support '-Aaliases'.\n" + install_msg)
 38 | 
 39 |     spath_proc = run_command(["clojure", "-Spath"], capture_output=False)
 40 |     if spath_proc.returncode != 0:
 41 |         raise RuntimeError("`clojure -Spath` failed; please upgrade to Clojure CLI ≥ 1.10.")
 42 | 
 43 | 
 44 | class ClojureLSP(SolidLanguageServer):
 45 |     """
 46 |     Provides a clojure-lsp specific instantiation of the LanguageServer class. Contains various configurations and settings specific to clojure.
 47 |     """
 48 | 
 49 |     clojure_lsp_releases = "https://github.com/clojure-lsp/clojure-lsp/releases/latest/download"
 50 |     runtime_dependencies = RuntimeDependencyCollection(
 51 |         [
 52 |             RuntimeDependency(
 53 |                 id="clojure-lsp",
 54 |                 url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-aarch64.zip",
 55 |                 platform_id="osx-arm64",
 56 |                 archive_type="zip",
 57 |                 binary_name="clojure-lsp",
 58 |             ),
 59 |             RuntimeDependency(
 60 |                 id="clojure-lsp",
 61 |                 url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-amd64.zip",
 62 |                 platform_id="osx-x64",
 63 |                 archive_type="zip",
 64 |                 binary_name="clojure-lsp",
 65 |             ),
 66 |             RuntimeDependency(
 67 |                 id="clojure-lsp",
 68 |                 url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-aarch64.zip",
 69 |                 platform_id="linux-arm64",
 70 |                 archive_type="zip",
 71 |                 binary_name="clojure-lsp",
 72 |             ),
 73 |             RuntimeDependency(
 74 |                 id="clojure-lsp",
 75 |                 url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-amd64.zip",
 76 |                 platform_id="linux-x64",
 77 |                 archive_type="zip",
 78 |                 binary_name="clojure-lsp",
 79 |             ),
 80 |             RuntimeDependency(
 81 |                 id="clojure-lsp",
 82 |                 url=f"{clojure_lsp_releases}/clojure-lsp-native-windows-amd64.zip",
 83 |                 platform_id="win-x64",
 84 |                 archive_type="zip",
 85 |                 binary_name="clojure-lsp.exe",
 86 |             ),
 87 |         ]
 88 |     )
 89 | 
 90 |     def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
 91 |         """
 92 |         Creates a ClojureLSP instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
 93 |         """
 94 |         clojure_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
 95 |         super().__init__(
 96 |             config,
 97 |             repository_root_path,
 98 |             ProcessLaunchInfo(cmd=clojure_lsp_executable_path, cwd=repository_root_path),
 99 |             "clojure",
100 |             solidlsp_settings,
101 |         )
102 |         self.server_ready = threading.Event()
103 |         self.initialize_searcher_command_available = threading.Event()
104 |         self.resolve_main_method_available = threading.Event()
105 |         self.service_ready_event = threading.Event()
106 | 
107 |     @classmethod
108 |     def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
109 |         """Setup runtime dependencies for clojure-lsp and return the command to start the server."""
110 |         verify_clojure_cli()
111 |         deps = ClojureLSP.runtime_dependencies
112 |         dependency = deps.get_single_dep_for_current_platform()
113 | 
114 |         clojurelsp_ls_dir = cls.ls_resources_dir(solidlsp_settings)
115 |         clojurelsp_executable_path = deps.binary_path(clojurelsp_ls_dir)
116 |         if not os.path.exists(clojurelsp_executable_path):
117 |             log.info(
118 |                 f"Downloading and extracting clojure-lsp from {dependency.url} to {clojurelsp_ls_dir}",
119 |             )
120 |             deps.install(clojurelsp_ls_dir)
121 |         if not os.path.exists(clojurelsp_executable_path):
122 |             raise FileNotFoundError(f"Download failed? Could not find clojure-lsp executable at {clojurelsp_executable_path}")
123 |         os.chmod(clojurelsp_executable_path, 0o755)
124 |         return clojurelsp_executable_path
125 | 
126 |     @staticmethod
127 |     def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
128 |         """Returns the init params for clojure-lsp."""
129 |         root_uri = pathlib.Path(repository_absolute_path).as_uri()
130 |         result = {  # type: ignore
131 |             "processId": os.getpid(),
132 |             "rootPath": repository_absolute_path,
133 |             "rootUri": root_uri,
134 |             "capabilities": {
135 |                 "workspace": {
136 |                     "applyEdit": True,
137 |                     "workspaceEdit": {"documentChanges": True},
138 |                     "symbol": {"symbolKind": {"valueSet": list(range(1, 27))}},
139 |                     "workspaceFolders": True,
140 |                 },
141 |                 "textDocument": {
142 |                     "synchronization": {"didSave": True},
143 |                     "publishDiagnostics": {"relatedInformation": True, "tagSupport": {"valueSet": [1, 2]}},
144 |                     "definition": {"linkSupport": True},
145 |                     "references": {},
146 |                     "hover": {"contentFormat": ["markdown", "plaintext"]},
147 |                     "documentSymbol": {
148 |                         "hierarchicalDocumentSymbolSupport": True,
149 |                         "symbolKind": {"valueSet": list(range(1, 27))},  #
150 |                     },
151 |                 },
152 |                 "general": {"positionEncodings": ["utf-16"]},
153 |             },
154 |             "initializationOptions": {"dependency-scheme": "jar", "text-document-sync-kind": "incremental"},
155 |             "trace": "off",
156 |             "workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
157 |         }
158 |         return cast(InitializeParams, result)
159 | 
160 |     def _start_server(self) -> None:
161 |         def register_capability_handler(params: dict) -> None:
162 |             assert "registrations" in params
163 |             for registration in params["registrations"]:
164 |                 if registration["method"] == "workspace/executeCommand":
165 |                     self.initialize_searcher_command_available.set()
166 |                     self.resolve_main_method_available.set()
167 |             return
168 | 
169 |         def lang_status_handler(params: dict) -> None:
170 |             # TODO: Should we wait for
171 |             # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
172 |             # Before proceeding?
173 |             if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
174 |                 self.service_ready_event.set()
175 | 
176 |         def execute_client_command_handler(params: dict) -> list:
177 |             return []
178 | 
179 |         def do_nothing(params: dict) -> None:
180 |             return
181 | 
182 |         def check_experimental_status(params: dict) -> None:
183 |             if params["quiescent"] is True:
184 |                 self.server_ready.set()
185 | 
186 |         def window_log_message(msg: dict) -> None:
187 |             log.info(f"LSP: window/logMessage: {msg}")
188 | 
189 |         self.server.on_request("client/registerCapability", register_capability_handler)
190 |         self.server.on_notification("language/status", lang_status_handler)
191 |         self.server.on_notification("window/logMessage", window_log_message)
192 |         self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
193 |         self.server.on_notification("$/progress", do_nothing)
194 |         self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
195 |         self.server.on_notification("language/actionableNotification", do_nothing)
196 |         self.server.on_notification("experimental/serverStatus", check_experimental_status)
197 | 
198 |         log.info("Starting clojure-lsp server process")
199 |         self.server.start()
200 | 
201 |         initialize_params = self._get_initialize_params(self.repository_root_path)
202 | 
203 |         log.info("Sending initialize request from LSP client to LSP server and awaiting response")
204 |         init_response = self.server.send.initialize(initialize_params)
205 |         assert init_response["capabilities"]["textDocumentSync"]["change"] == 2  # type: ignore
206 |         assert "completionProvider" in init_response["capabilities"]
207 |         # Clojure-lsp completion provider capabilities are more flexible than other servers'
208 |         completion_provider = init_response["capabilities"]["completionProvider"]
209 |         assert completion_provider["resolveProvider"] is True
210 |         assert "triggerCharacters" in completion_provider
211 |         self.server.notify.initialized({})
212 |         # after initialize, Clojure-lsp is ready to serve
213 |         self.server_ready.set()
214 |         self.completions_available.set()
215 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/clangd_language_server.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++.
  3 | """
  4 | 
  5 | import logging
  6 | import os
  7 | import pathlib
  8 | import threading
  9 | from typing import Any, cast
 10 | 
 11 | from solidlsp.ls import SolidLanguageServer
 12 | from solidlsp.ls_config import LanguageServerConfig
 13 | from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
 14 | from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
 15 | from solidlsp.settings import SolidLSPSettings
 16 | 
 17 | from .common import RuntimeDependency, RuntimeDependencyCollection
 18 | 
 19 | log = logging.getLogger(__name__)
 20 | 
 21 | 
 22 | class ClangdLanguageServer(SolidLanguageServer):
 23 |     """
 24 |     Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++.
 25 |     As the project gets bigger in size, building index will take time. Try running clangd multiple times to ensure index is built properly.
 26 |     Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example.
 27 |     """
 28 | 
 29 |     def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
 30 |         """
 31 |         Creates a ClangdLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
 32 |         """
 33 |         clangd_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
 34 |         super().__init__(
 35 |             config, repository_root_path, ProcessLaunchInfo(cmd=clangd_executable_path, cwd=repository_root_path), "cpp", solidlsp_settings
 36 |         )
 37 |         self.server_ready = threading.Event()
 38 |         self.service_ready_event = threading.Event()
 39 |         self.initialize_searcher_command_available = threading.Event()
 40 |         self.resolve_main_method_available = threading.Event()
 41 | 
 42 |     @classmethod
 43 |     def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
 44 |         """
 45 |         Setup runtime dependencies for ClangdLanguageServer and return the command to start the server.
 46 |         """
 47 |         import shutil
 48 | 
 49 |         deps = RuntimeDependencyCollection(
 50 |             [
 51 |                 RuntimeDependency(
 52 |                     id="Clangd",
 53 |                     description="Clangd for Linux (x64)",
 54 |                     url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-linux-19.1.2.zip",
 55 |                     platform_id="linux-x64",
 56 |                     archive_type="zip",
 57 |                     binary_name="clangd_19.1.2/bin/clangd",
 58 |                 ),
 59 |                 RuntimeDependency(
 60 |                     id="Clangd",
 61 |                     description="Clangd for Windows (x64)",
 62 |                     url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-windows-19.1.2.zip",
 63 |                     platform_id="win-x64",
 64 |                     archive_type="zip",
 65 |                     binary_name="clangd_19.1.2/bin/clangd.exe",
 66 |                 ),
 67 |                 RuntimeDependency(
 68 |                     id="Clangd",
 69 |                     description="Clangd for macOS (x64)",
 70 |                     url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip",
 71 |                     platform_id="osx-x64",
 72 |                     archive_type="zip",
 73 |                     binary_name="clangd_19.1.2/bin/clangd",
 74 |                 ),
 75 |                 RuntimeDependency(
 76 |                     id="Clangd",
 77 |                     description="Clangd for macOS (Arm64)",
 78 |                     url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip",
 79 |                     platform_id="osx-arm64",
 80 |                     archive_type="zip",
 81 |                     binary_name="clangd_19.1.2/bin/clangd",
 82 |                 ),
 83 |             ]
 84 |         )
 85 | 
 86 |         clangd_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "clangd")
 87 | 
 88 |         try:
 89 |             dep = deps.get_single_dep_for_current_platform()
 90 |         except RuntimeError:
 91 |             dep = None
 92 | 
 93 |         if dep is None:
 94 |             # No prebuilt binary available, look for system-installed clangd
 95 |             clangd_executable_path = shutil.which("clangd")
 96 |             if not clangd_executable_path:
 97 |                 raise FileNotFoundError(
 98 |                     "Clangd is not installed on your system.\n"
 99 |                     + "Please install clangd using your system package manager:\n"
100 |                     + "  Ubuntu/Debian: sudo apt-get install clangd\n"
101 |                     + "  Fedora/RHEL: sudo dnf install clang-tools-extra\n"
102 |                     + "  Arch Linux: sudo pacman -S clang\n"
103 |                     + "See https://clangd.llvm.org/installation for more details."
104 |                 )
105 |             log.info(f"Using system-installed clangd at {clangd_executable_path}")
106 |         else:
107 |             # Standard download and install for platforms with prebuilt binaries
108 |             clangd_executable_path = deps.binary_path(clangd_ls_dir)
109 |             if not os.path.exists(clangd_executable_path):
110 |                 log.info(f"Clangd executable not found at {clangd_executable_path}. Downloading from {dep.url}")
111 |                 _ = deps.install(clangd_ls_dir)
112 |             if not os.path.exists(clangd_executable_path):
113 |                 raise FileNotFoundError(
114 |                     f"Clangd executable not found at {clangd_executable_path}.\n"
115 |                     + "Make sure you have installed clangd. See https://clangd.llvm.org/installation"
116 |                 )
117 |             os.chmod(clangd_executable_path, 0o755)
118 |         return clangd_executable_path
119 | 
120 |     @staticmethod
121 |     def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
122 |         """
123 |         Returns the initialize params for the clangd Language Server.
124 |         """
125 |         root_uri = pathlib.Path(repository_absolute_path).as_uri()
126 |         initialize_params = {
127 |             "locale": "en",
128 |             "capabilities": {
129 |                 "textDocument": {
130 |                     "synchronization": {"didSave": True, "dynamicRegistration": True},
131 |                     "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
132 |                     "definition": {"dynamicRegistration": True},
133 |                 },
134 |                 "workspace": {"workspaceFolders": True, "didChangeConfiguration": {"dynamicRegistration": True}},
135 |             },
136 |             "processId": os.getpid(),
137 |             "rootPath": repository_absolute_path,
138 |             "rootUri": root_uri,
139 |             "workspaceFolders": [
140 |                 {
141 |                     "uri": root_uri,
142 |                     "name": "$name",
143 |                 }
144 |             ],
145 |         }
146 | 
147 |         return cast(InitializeParams, initialize_params)
148 | 
149 |     def _start_server(self) -> None:
150 |         """
151 |         Starts the Clangd Language Server, waits for the server to be ready and yields the LanguageServer instance.
152 | 
153 |         Usage:
154 |         ```
155 |         async with lsp.start_server():
156 |             # LanguageServer has been initialized and ready to serve requests
157 |             await lsp.request_definition(...)
158 |             await lsp.request_references(...)
159 |             # Shutdown the LanguageServer on exit from scope
160 |         # LanguageServer has been shutdown
161 |         """
162 | 
163 |         def register_capability_handler(params: Any) -> None:
164 |             assert "registrations" in params
165 |             for registration in params["registrations"]:
166 |                 if registration["method"] == "workspace/executeCommand":
167 |                     self.initialize_searcher_command_available.set()
168 |                     self.resolve_main_method_available.set()
169 |             return
170 | 
171 |         def lang_status_handler(params: Any) -> None:
172 |             # TODO: Should we wait for
173 |             # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
174 |             # Before proceeding?
175 |             if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
176 |                 self.service_ready_event.set()
177 | 
178 |         def execute_client_command_handler(params: Any) -> list:
179 |             return []
180 | 
181 |         def do_nothing(params: Any) -> None:
182 |             return
183 | 
184 |         def check_experimental_status(params: Any) -> None:
185 |             if params["quiescent"] == True:
186 |                 self.server_ready.set()
187 | 
188 |         def window_log_message(msg: dict) -> None:
189 |             log.info(f"LSP: window/logMessage: {msg}")
190 | 
191 |         self.server.on_request("client/registerCapability", register_capability_handler)
192 |         self.server.on_notification("language/status", lang_status_handler)
193 |         self.server.on_notification("window/logMessage", window_log_message)
194 |         self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
195 |         self.server.on_notification("$/progress", do_nothing)
196 |         self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
197 |         self.server.on_notification("language/actionableNotification", do_nothing)
198 |         self.server.on_notification("experimental/serverStatus", check_experimental_status)
199 | 
200 |         log.info("Starting Clangd server process")
201 |         self.server.start()
202 |         initialize_params = self._get_initialize_params(self.repository_root_path)
203 | 
204 |         log.info("Sending initialize request from LSP client to LSP server and awaiting response")
205 |         init_response = self.server.send.initialize(initialize_params)
206 |         assert init_response["capabilities"]["textDocumentSync"]["change"] == 2  # type: ignore
207 |         assert "completionProvider" in init_response["capabilities"]
208 |         assert init_response["capabilities"]["completionProvider"] == {
209 |             "triggerCharacters": [".", "<", ">", ":", '"', "/", "*"],
210 |             "resolveProvider": False,
211 |         }
212 | 
213 |         self.server.notify.initialized({})
214 | 
215 |         self.completions_available.set()
216 |         # set ready flag
217 |         self.server_ready.set()
218 |         self.server_ready.wait()
219 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/pascal/test_pascal_basic.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Basic integration tests for the Pascal language server functionality.
  3 | 
  4 | These tests validate the functionality of the language server APIs
  5 | like request_document_symbols using the Pascal test repository.
  6 | 
  7 | Uses genericptr/pascal-language-server which returns SymbolInformation[] format:
  8 | - Returns classes, structs, enums, typedefs, functions/procedures
  9 | - Uses correct SymbolKind values: Class=5, Function=12, Method=6, Struct=23
 10 | - Method names don't include parent class prefix; uses containerName instead
 11 | """
 12 | 
 13 | import pytest
 14 | 
 15 | from solidlsp import SolidLanguageServer
 16 | from solidlsp.ls_config import Language
 17 | from solidlsp.ls_types import SymbolKind
 18 | from test.conftest import language_tests_enabled
 19 | 
 20 | pytestmark = [
 21 |     pytest.mark.pascal,
 22 |     pytest.mark.skipif(not language_tests_enabled(Language.PASCAL), reason="Pascal tests are disabled (pasls/fpc not available)"),
 23 | ]
 24 | 
 25 | 
 26 | @pytest.mark.pascal
 27 | class TestPascalLanguageServerBasics:
 28 |     """Test basic functionality of the Pascal language server."""
 29 | 
 30 |     @pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
 31 |     def test_pascal_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
 32 |         """Test that Pascal language server can be initialized successfully."""
 33 |         assert language_server is not None
 34 |         assert language_server.language == Language.PASCAL
 35 | 
 36 |     @pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
 37 |     def test_pascal_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
 38 |         """Test request_document_symbols for Pascal files.
 39 | 
 40 |         genericptr pasls returns proper SymbolKind values:
 41 |         - Standalone functions: kind=12 (Function)
 42 |         - Classes: kind=5 (Class)
 43 |         """
 44 |         # Test getting symbols from main.pas
 45 |         all_symbols, _root_symbols = language_server.request_document_symbols("main.pas").get_all_symbols_and_roots()
 46 | 
 47 |         # Should have symbols
 48 |         assert len(all_symbols) > 0, "Should have symbols in main.pas"
 49 | 
 50 |         # Should detect standalone functions (SymbolKind.Function = 12)
 51 |         function_symbols = [s for s in all_symbols if s.get("kind") == SymbolKind.Function]
 52 |         function_names = [s["name"] for s in function_symbols]
 53 | 
 54 |         assert "CalculateSum" in function_names, "Should find CalculateSum function"
 55 |         assert "PrintMessage" in function_names, "Should find PrintMessage procedure"
 56 | 
 57 |         # Should detect classes (SymbolKind.Class = 5)
 58 |         class_symbols = [s for s in all_symbols if s.get("kind") == SymbolKind.Class]
 59 |         class_names = [s["name"] for s in class_symbols]
 60 | 
 61 |         assert "TUser" in class_names, "Should find TUser class"
 62 |         assert "TUserManager" in class_names, "Should find TUserManager class"
 63 | 
 64 |     @pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
 65 |     def test_pascal_class_methods(self, language_server: SolidLanguageServer) -> None:
 66 |         """Test detection of class methods in Pascal files.
 67 | 
 68 |         pasls returns class methods with SymbolKind.Method (kind 6), not Function (kind 12).
 69 |         """
 70 |         all_symbols, _root_symbols = language_server.request_document_symbols("main.pas").get_all_symbols_and_roots()
 71 | 
 72 |         # Get all method symbols (pasls returns class methods as SymbolKind.Method = 6)
 73 |         method_symbols = [s for s in all_symbols if s.get("kind") == SymbolKind.Method]
 74 |         method_names = [s["name"] for s in method_symbols]
 75 | 
 76 |         # Should detect TUser methods
 77 |         expected_tuser_methods = ["Create", "Destroy", "GetInfo", "UpdateAge"]
 78 |         for method in expected_tuser_methods:
 79 |             found = method in method_names
 80 |             assert found, f"Should find method '{method}'"
 81 | 
 82 |         # Should detect TUserManager methods
 83 |         expected_manager_methods = ["Create", "Destroy", "AddUser", "GetUserCount", "FindUserByName"]
 84 |         for method in expected_manager_methods:
 85 |             found = method in method_names
 86 |             assert found, f"Should find method '{method}'"
 87 | 
 88 |     @pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
 89 |     def test_pascal_helper_unit_symbols(self, language_server: SolidLanguageServer) -> None:
 90 |         """Test function detection in Helper unit."""
 91 |         # Test with lib/helper.pas
 92 |         helper_all_symbols, _helper_root_symbols = language_server.request_document_symbols("lib/helper.pas").get_all_symbols_and_roots()
 93 | 
 94 |         # Should have symbols
 95 |         assert len(helper_all_symbols) > 0, "Helper unit should have symbols"
 96 | 
 97 |         # Extract function symbols
 98 |         function_symbols = [s for s in helper_all_symbols if s.get("kind") == SymbolKind.Function]
 99 |         function_names = [s["name"] for s in function_symbols]
100 | 
101 |         # Should detect standalone functions
102 |         expected_functions = ["GetHelperMessage", "MultiplyNumbers", "LogMessage"]
103 |         for func_name in expected_functions:
104 |             assert func_name in function_names, f"Should find {func_name} function in Helper unit"
105 | 
106 |         # Should also detect THelper class methods (returned as SymbolKind.Method = 6)
107 |         method_symbols = [s for s in helper_all_symbols if s.get("kind") == SymbolKind.Method]
108 |         method_names = [s["name"] for s in method_symbols]
109 |         assert "FormatString" in method_names, "Should find FormatString method"
110 |         assert "IsEven" in method_names, "Should find IsEven method"
111 | 
112 |     @pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
113 |     def test_pascal_cross_file_references(self, language_server: SolidLanguageServer) -> None:
114 |         """Test that Pascal LSP can handle cross-file references."""
115 |         # main.pas uses Helper unit
116 |         main_symbols, _main_roots = language_server.request_document_symbols("main.pas").get_all_symbols_and_roots()
117 |         helper_symbols, _helper_roots = language_server.request_document_symbols("lib/helper.pas").get_all_symbols_and_roots()
118 | 
119 |         # Verify both files have symbols
120 |         assert len(main_symbols) > 0, "main.pas should have symbols"
121 |         assert len(helper_symbols) > 0, "helper.pas should have symbols"
122 | 
123 |         # Verify GetHelperMessage is in Helper unit
124 |         helper_function_names = [s["name"] for s in helper_symbols if s.get("kind") == SymbolKind.Function]
125 |         assert "GetHelperMessage" in helper_function_names, "Helper unit should export GetHelperMessage"
126 | 
127 |     @pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
128 |     def test_pascal_symbol_locations(self, language_server: SolidLanguageServer) -> None:
129 |         """Test that symbols have correct location information.
130 | 
131 |         Note: genericptr pasls returns the interface declaration location (line ~41),
132 |         not the implementation location (line ~115).
133 |         """
134 |         all_symbols, _root_symbols = language_server.request_document_symbols("main.pas").get_all_symbols_and_roots()
135 | 
136 |         # Find CalculateSum function
137 |         calc_symbols = [s for s in all_symbols if s.get("name") == "CalculateSum"]
138 |         assert len(calc_symbols) > 0, "Should find CalculateSum"
139 | 
140 |         calc_symbol = calc_symbols[0]
141 | 
142 |         # Verify it has location information (SymbolInformation format uses location.range)
143 |         if "location" in calc_symbol:
144 |             location = calc_symbol["location"]
145 |             assert "range" in location, "Location should have range"
146 |             assert "start" in location["range"], "Range should have start"
147 |             assert "line" in location["range"]["start"], "Start should have line"
148 |             line = location["range"]["start"]["line"]
149 |         else:
150 |             # DocumentSymbol format uses range directly
151 |             assert "range" in calc_symbol, "Symbol should have range"
152 |             assert "start" in calc_symbol["range"], "Range should have start"
153 |             line = calc_symbol["range"]["start"]["line"]
154 | 
155 |         # CalculateSum is declared at line 41 in main.pas (0-indexed would be 40)
156 |         # genericptr pasls returns interface declaration location
157 |         assert 35 <= line <= 45, f"CalculateSum should be around line 41 (interface), got {line}"
158 | 
159 |     @pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
160 |     def test_pascal_namespace_symbol(self, language_server: SolidLanguageServer) -> None:
161 |         """Test that genericptr pasls returns Interface namespace symbol."""
162 |         all_symbols, _root_symbols = language_server.request_document_symbols("main.pas").get_all_symbols_and_roots()
163 | 
164 |         # genericptr pasls adds an "Interface" namespace symbol
165 |         symbol_names = [s["name"] for s in all_symbols]
166 | 
167 |         # The Interface section should be represented
168 |         # Note: This depends on pasls configuration
169 |         assert len(all_symbols) > 0, "Should have symbols"
170 |         # Interface namespace may or may not be present depending on pasls configuration
171 |         _ = symbol_names  # used for potential future assertions
172 | 
173 |     @pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
174 |     def test_pascal_hover_with_doc_comments(self, language_server: SolidLanguageServer) -> None:
175 |         """Test that hover returns documentation comments.
176 | 
177 |         CalculateSum has /// style doc comments that should appear in hover.
178 |         """
179 |         # CalculateSum is declared at line 46 (1-indexed), so line 45 (0-indexed)
180 |         hover = language_server.request_hover("main.pas", 45, 12)
181 | 
182 |         assert hover is not None, "Hover should return a result"
183 | 
184 |         # Extract hover content - handle both dict and object formats
185 |         if isinstance(hover, dict):
186 |             contents = hover.get("contents", {})
187 |             value = contents.get("value", "") if isinstance(contents, dict) else str(contents)
188 |         else:
189 |             value = hover.contents.value if hasattr(hover.contents, "value") else str(hover.contents)
190 | 
191 |         # Should contain the function signature
192 |         assert "CalculateSum" in value, f"Hover should show function name. Got: {value[:500]}"
193 | 
194 |         # Should contain the doc comment
195 |         assert "Calculates the sum" in value, f"Hover should include doc comment. Got: {value[:500]}"
196 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/terraform_ls.py:
--------------------------------------------------------------------------------

```python
  1 | import logging
  2 | import os
  3 | import shutil
  4 | import threading
  5 | from typing import cast
  6 | 
  7 | from overrides import override
  8 | 
  9 | from solidlsp.ls import SolidLanguageServer
 10 | from solidlsp.ls_config import LanguageServerConfig
 11 | from solidlsp.ls_utils import PathUtils, PlatformUtils
 12 | from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
 13 | from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
 14 | from solidlsp.settings import SolidLSPSettings
 15 | 
 16 | from .common import RuntimeDependency, RuntimeDependencyCollection
 17 | 
 18 | log = logging.getLogger(__name__)
 19 | 
 20 | 
 21 | class TerraformLS(SolidLanguageServer):
 22 |     """
 23 |     Provides Terraform specific instantiation of the LanguageServer class using terraform-ls.
 24 |     """
 25 | 
 26 |     @override
 27 |     def is_ignored_dirname(self, dirname: str) -> bool:
 28 |         return super().is_ignored_dirname(dirname) or dirname in [".terraform", "terraform.tfstate.d"]
 29 | 
 30 |     @staticmethod
 31 |     def _determine_log_level(line: str) -> int:
 32 |         """Classify terraform-ls stderr output to avoid false-positive errors."""
 33 |         line_lower = line.lower()
 34 | 
 35 |         # File discovery messages that are not actual errors
 36 |         if any(
 37 |             [
 38 |                 "discover.go:" in line_lower,
 39 |                 "walker.go:" in line_lower,
 40 |                 "walking of {file://" in line_lower,
 41 |                 "bus: -> discover" in line_lower,
 42 |             ]
 43 |         ):
 44 |             return logging.DEBUG
 45 | 
 46 |         # Known informational messages from terraform-ls that contain "error" but aren't errors
 47 |         # Note: pattern match is flexible to handle file paths between keywords
 48 |         if any(
 49 |             [
 50 |                 "loading module metadata returned error:" in line_lower and "state not changed" in line_lower,
 51 |                 "incoming notification for" in line_lower,
 52 |             ]
 53 |         ):
 54 |             return logging.DEBUG
 55 | 
 56 |         return SolidLanguageServer._determine_log_level(line)
 57 | 
 58 |     @staticmethod
 59 |     def _ensure_tf_command_available() -> None:
 60 |         log.debug("Starting terraform version detection...")
 61 | 
 62 |         # 1. Try to find terraform using shutil.which
 63 |         terraform_cmd = shutil.which("terraform")
 64 |         if terraform_cmd is not None:
 65 |             log.debug(f"Found terraform via shutil.which: {terraform_cmd}")
 66 |             return
 67 | 
 68 |         # TODO: is this needed?
 69 |         # 2. Fallback to TERRAFORM_CLI_PATH (set by hashicorp/setup-terraform action)
 70 |         if not terraform_cmd:
 71 |             terraform_cli_path = os.environ.get("TERRAFORM_CLI_PATH")
 72 |             if terraform_cli_path:
 73 |                 log.debug(f"Trying TERRAFORM_CLI_PATH: {terraform_cli_path}")
 74 |                 # TODO: use binary name from runtime dependencies if we keep this code
 75 |                 if os.name == "nt":
 76 |                     terraform_binary = os.path.join(terraform_cli_path, "terraform.exe")
 77 |                 else:
 78 |                     terraform_binary = os.path.join(terraform_cli_path, "terraform")
 79 |                 if os.path.exists(terraform_binary):
 80 |                     terraform_cmd = terraform_binary
 81 |                     log.debug(f"Found terraform via TERRAFORM_CLI_PATH: {terraform_cmd}")
 82 |                     return
 83 | 
 84 |         raise RuntimeError(
 85 |             "Terraform executable not found, please ensure Terraform is installed."
 86 |             "See https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli for instructions."
 87 |         )
 88 | 
 89 |     @classmethod
 90 |     def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
 91 |         """
 92 |         Setup runtime dependencies for terraform-ls.
 93 |         Downloads and installs terraform-ls if not already present.
 94 |         """
 95 |         cls._ensure_tf_command_available()
 96 |         platform_id = PlatformUtils.get_platform_id()
 97 |         deps = RuntimeDependencyCollection(
 98 |             [
 99 |                 RuntimeDependency(
100 |                     id="TerraformLS",
101 |                     description="terraform-ls for macOS (ARM64)",
102 |                     url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_darwin_arm64.zip",
103 |                     platform_id="osx-arm64",
104 |                     archive_type="zip",
105 |                     binary_name="terraform-ls",
106 |                 ),
107 |                 RuntimeDependency(
108 |                     id="TerraformLS",
109 |                     description="terraform-ls for macOS (x64)",
110 |                     url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_darwin_amd64.zip",
111 |                     platform_id="osx-x64",
112 |                     archive_type="zip",
113 |                     binary_name="terraform-ls",
114 |                 ),
115 |                 RuntimeDependency(
116 |                     id="TerraformLS",
117 |                     description="terraform-ls for Linux (ARM64)",
118 |                     url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_linux_arm64.zip",
119 |                     platform_id="linux-arm64",
120 |                     archive_type="zip",
121 |                     binary_name="terraform-ls",
122 |                 ),
123 |                 RuntimeDependency(
124 |                     id="TerraformLS",
125 |                     description="terraform-ls for Linux (x64)",
126 |                     url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_linux_amd64.zip",
127 |                     platform_id="linux-x64",
128 |                     archive_type="zip",
129 |                     binary_name="terraform-ls",
130 |                 ),
131 |                 RuntimeDependency(
132 |                     id="TerraformLS",
133 |                     description="terraform-ls for Windows (x64)",
134 |                     url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_windows_amd64.zip",
135 |                     platform_id="win-x64",
136 |                     archive_type="zip",
137 |                     binary_name="terraform-ls.exe",
138 |                 ),
139 |             ]
140 |         )
141 |         dependency = deps.get_single_dep_for_current_platform()
142 | 
143 |         terraform_ls_executable_path = deps.binary_path(cls.ls_resources_dir(solidlsp_settings))
144 |         if not os.path.exists(terraform_ls_executable_path):
145 |             log.info(f"Downloading terraform-ls from {dependency.url}")
146 |             deps.install(cls.ls_resources_dir(solidlsp_settings))
147 | 
148 |         assert os.path.exists(terraform_ls_executable_path), f"terraform-ls executable not found at {terraform_ls_executable_path}"
149 | 
150 |         # Make the executable file executable on Unix-like systems
151 |         if platform_id.value != "win-x64":
152 |             os.chmod(terraform_ls_executable_path, 0o755)
153 | 
154 |         return terraform_ls_executable_path
155 | 
156 |     def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
157 |         """
158 |         Creates a TerraformLS instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
159 |         """
160 |         terraform_ls_executable_path = self._setup_runtime_dependencies(solidlsp_settings)
161 | 
162 |         super().__init__(
163 |             config,
164 |             repository_root_path,
165 |             ProcessLaunchInfo(cmd=f"{terraform_ls_executable_path} serve", cwd=repository_root_path),
166 |             "terraform",
167 |             solidlsp_settings,
168 |         )
169 |         self.server_ready = threading.Event()
170 |         self.request_id = 0
171 | 
172 |     @staticmethod
173 |     def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
174 |         """
175 |         Returns the initialize params for the Terraform Language Server.
176 |         """
177 |         root_uri = PathUtils.path_to_uri(repository_absolute_path)
178 |         result = {
179 |             "processId": os.getpid(),
180 |             "locale": "en",
181 |             "rootPath": repository_absolute_path,
182 |             "rootUri": root_uri,
183 |             "capabilities": {
184 |                 "textDocument": {
185 |                     "synchronization": {"didSave": True, "dynamicRegistration": True},
186 |                     "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
187 |                     "definition": {"dynamicRegistration": True},
188 |                     "documentSymbol": {
189 |                         "dynamicRegistration": True,
190 |                         "hierarchicalDocumentSymbolSupport": True,
191 |                         "symbolKind": {"valueSet": list(range(1, 27))},
192 |                     },
193 |                 },
194 |                 "workspace": {"workspaceFolders": True, "didChangeConfiguration": {"dynamicRegistration": True}},
195 |             },
196 |             "workspaceFolders": [
197 |                 {
198 |                     "name": os.path.basename(repository_absolute_path),
199 |                     "uri": root_uri,
200 |                 }
201 |             ],
202 |         }
203 |         return cast(InitializeParams, result)
204 | 
205 |     def _start_server(self) -> None:
206 |         """Start terraform-ls server process"""
207 | 
208 |         def register_capability_handler(params: dict) -> None:
209 |             return
210 | 
211 |         def window_log_message(msg: dict) -> None:
212 |             log.info(f"LSP: window/logMessage: {msg}")
213 | 
214 |         def do_nothing(params: dict) -> None:
215 |             return
216 | 
217 |         self.server.on_request("client/registerCapability", register_capability_handler)
218 |         self.server.on_notification("window/logMessage", window_log_message)
219 |         self.server.on_notification("$/progress", do_nothing)
220 |         self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
221 | 
222 |         log.info("Starting terraform-ls server process")
223 |         self.server.start()
224 |         initialize_params = self._get_initialize_params(self.repository_root_path)
225 | 
226 |         log.info("Sending initialize request from LSP client to LSP server and awaiting response")
227 |         init_response = self.server.send.initialize(initialize_params)
228 | 
229 |         # Verify server capabilities
230 |         assert "textDocumentSync" in init_response["capabilities"]
231 |         assert "completionProvider" in init_response["capabilities"]
232 |         assert "definitionProvider" in init_response["capabilities"]
233 | 
234 |         self.server.notify.initialized({})
235 |         self.completions_available.set()
236 | 
237 |         # terraform-ls server is typically ready immediately after initialization
238 |         self.server_ready.set()
239 |         self.server_ready.wait()
240 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/toml/test_toml_symbol_retrieval.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Tests for TOML language server symbol retrieval functionality.
  3 | 
  4 | These tests focus on advanced symbol operations:
  5 | - request_containing_symbol
  6 | - request_document_overview
  7 | - request_full_symbol_tree
  8 | - request_dir_overview
  9 | """
 10 | 
 11 | from pathlib import Path
 12 | 
 13 | import pytest
 14 | 
 15 | from solidlsp import SolidLanguageServer
 16 | from solidlsp.ls_config import Language
 17 | 
 18 | pytestmark = pytest.mark.toml
 19 | 
 20 | 
 21 | class TestTomlSymbolRetrieval:
 22 |     """Test advanced symbol retrieval functionality for TOML files."""
 23 | 
 24 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
 25 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
 26 |     def test_request_containing_symbol_behavior(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 27 |         """Test request_containing_symbol behavior for TOML files.
 28 | 
 29 |         Note: Taplo LSP doesn't support definition/containing symbol lookups for TOML files
 30 |         since TOML is a configuration format, not code. This test verifies the behavior.
 31 |         """
 32 |         # Line 2 (0-indexed: 1) is inside the [package] table
 33 |         containing_symbol = language_server.request_containing_symbol("Cargo.toml", 1, 5)
 34 | 
 35 |         # Taplo doesn't support containing symbol lookup - returns None
 36 |         # This is expected behavior for a configuration file format
 37 |         assert containing_symbol is None, "TOML LSP doesn't support containing symbol lookup"
 38 | 
 39 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
 40 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
 41 |     def test_request_document_overview_cargo(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 42 |         """Test request_document_overview for Cargo.toml."""
 43 |         overview = language_server.request_document_overview("Cargo.toml")
 44 | 
 45 |         assert overview is not None
 46 |         assert len(overview) > 0
 47 | 
 48 |         # Get symbol names from overview
 49 |         symbol_names = {symbol.get("name") for symbol in overview if "name" in symbol}
 50 | 
 51 |         # Verify expected top-level tables appear
 52 |         expected_tables = {"package", "dependencies", "dev-dependencies", "features", "workspace"}
 53 |         assert expected_tables.issubset(symbol_names), f"Missing expected tables in overview: {expected_tables - symbol_names}"
 54 | 
 55 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
 56 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
 57 |     def test_request_document_overview_pyproject(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 58 |         """Test request_document_overview for pyproject.toml."""
 59 |         overview = language_server.request_document_overview("pyproject.toml")
 60 | 
 61 |         assert overview is not None
 62 |         assert len(overview) > 0
 63 | 
 64 |         # Get symbol names from overview
 65 |         symbol_names = {symbol.get("name") for symbol in overview if "name" in symbol}
 66 | 
 67 |         # Verify expected top-level tables appear
 68 |         assert "project" in symbol_names, "Should detect 'project' table"
 69 |         assert "build-system" in symbol_names, "Should detect 'build-system' table"
 70 | 
 71 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
 72 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
 73 |     def test_request_full_symbol_tree(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 74 |         """Test request_full_symbol_tree returns TOML files."""
 75 |         symbol_tree = language_server.request_full_symbol_tree()
 76 | 
 77 |         assert symbol_tree is not None
 78 |         assert len(symbol_tree) > 0
 79 | 
 80 |         # The root should be test_repo
 81 |         root = symbol_tree[0]
 82 |         assert root["name"] == "test_repo"
 83 |         assert "children" in root
 84 | 
 85 |         # Children should include TOML files
 86 |         child_names = {child["name"] for child in root.get("children", [])}
 87 |         # Note: File names are stripped of extension in some cases
 88 |         assert (
 89 |             "Cargo" in child_names or "Cargo.toml" in child_names or any("cargo" in name.lower() for name in child_names)
 90 |         ), f"Should find Cargo.toml in tree, got: {child_names}"
 91 | 
 92 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
 93 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
 94 |     def test_request_dir_overview(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 95 |         """Test request_dir_overview returns symbols for TOML files."""
 96 |         overview = language_server.request_dir_overview(".")
 97 | 
 98 |         assert overview is not None
 99 |         assert len(overview) > 0
100 | 
101 |         # Should have entries for both Cargo.toml and pyproject.toml
102 |         file_paths = list(overview.keys())
103 |         assert any("Cargo.toml" in path for path in file_paths), f"Should find Cargo.toml in overview, got: {file_paths}"
104 |         assert any("pyproject.toml" in path for path in file_paths), f"Should find pyproject.toml in overview, got: {file_paths}"
105 | 
106 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
107 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
108 |     def test_symbol_hierarchy_in_cargo(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
109 |         """Test that symbol hierarchy is properly preserved in Cargo.toml."""
110 |         all_symbols, root_symbols = language_server.request_document_symbols("Cargo.toml").get_all_symbols_and_roots()
111 | 
112 |         # Find the 'package' table
113 |         package_symbol = next((s for s in root_symbols if s.get("name") == "package"), None)
114 |         assert package_symbol is not None, "Should find 'package' as root symbol"
115 | 
116 |         # Verify it has children (nested keys)
117 |         assert "children" in package_symbol, "'package' should have children"
118 |         child_names = {child.get("name") for child in package_symbol.get("children", [])}
119 | 
120 |         # Package should have name, version, edition at minimum
121 |         assert "name" in child_names, "'package' should have 'name' child"
122 |         assert "version" in child_names, "'package' should have 'version' child"
123 |         assert "edition" in child_names, "'package' should have 'edition' child"
124 | 
125 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
126 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
127 |     def test_symbol_hierarchy_in_pyproject(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
128 |         """Test that symbol hierarchy is properly preserved in pyproject.toml."""
129 |         all_symbols, root_symbols = language_server.request_document_symbols("pyproject.toml").get_all_symbols_and_roots()
130 | 
131 |         # Find the 'project' table
132 |         project_symbol = next((s for s in root_symbols if s.get("name") == "project"), None)
133 |         assert project_symbol is not None, "Should find 'project' as root symbol"
134 | 
135 |         # Verify it has children
136 |         assert "children" in project_symbol, "'project' should have children"
137 |         child_names = {child.get("name") for child in project_symbol.get("children", [])}
138 | 
139 |         # Project should have name, version, dependencies at minimum
140 |         assert "name" in child_names, "'project' should have 'name' child"
141 |         assert "version" in child_names, "'project' should have 'version' child"
142 | 
143 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
144 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
145 |     def test_tool_section_hierarchy(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
146 |         """Test that tool sections in pyproject.toml are properly structured."""
147 |         all_symbols, root_symbols = language_server.request_document_symbols("pyproject.toml").get_all_symbols_and_roots()
148 | 
149 |         # Get all symbol names
150 |         all_names = [s.get("name") for s in all_symbols]
151 | 
152 |         # Should detect tool.ruff, tool.mypy, or tool.pytest
153 |         has_ruff = any("ruff" in name.lower() for name in all_names if name)
154 |         has_mypy = any("mypy" in name.lower() for name in all_names if name)
155 |         has_pytest = any("pytest" in name.lower() for name in all_names if name)
156 | 
157 |         assert has_ruff or has_mypy or has_pytest, f"Should detect tool sections, got names: {all_names}"
158 | 
159 |     @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
160 |     @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
161 |     def test_array_of_tables_symbol(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
162 |         """Test that [[bin]] array of tables is detected."""
163 |         all_symbols, root_symbols = language_server.request_document_symbols("Cargo.toml").get_all_symbols_and_roots()
164 | 
165 |         # Get all symbol names
166 |         all_names = [s.get("name") for s in all_symbols]
167 | 
168 |         # Should detect bin array of tables
169 |         has_bin = "bin" in all_names
170 |         assert has_bin, f"Should detect [[bin]] array of tables, got names: {all_names}"
171 | 
172 |         # Find the bin symbol and verify its structure
173 |         bin_symbol = next((s for s in all_symbols if s.get("name") == "bin"), None)
174 |         assert bin_symbol is not None, "Should find bin symbol"
175 | 
176 |         # Array of tables should be kind 18 (array)
177 |         assert bin_symbol.get("kind") == 18, "[[bin]] should have kind 18 (array)"
178 | 
179 |         # Children of array of tables are indexed by position ('0', '1', etc.)
180 |         if "children" in bin_symbol:
181 |             bin_children = bin_symbol.get("children", [])
182 |             assert len(bin_children) > 0, "[[bin]] should have at least one child element"
183 |             # First child is index '0'
184 |             first_child = bin_children[0]
185 |             assert first_child.get("name") == "0", f"First array element should be named '0', got: {first_child.get('name')}"
186 | 
187 |             # The '0' element should contain name and path as grandchildren
188 |             if "children" in first_child:
189 |                 grandchild_names = {gc.get("name") for gc in first_child.get("children", [])}
190 |                 assert "name" in grandchild_names, f"[[bin]] element should have 'name' field, got: {grandchild_names}"
191 |                 assert "path" in grandchild_names, f"[[bin]] element should have 'path' field, got: {grandchild_names}"
192 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/zls.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Provides Zig specific instantiation of the LanguageServer class using ZLS (Zig Language Server).
  3 | """
  4 | 
  5 | import logging
  6 | import os
  7 | import pathlib
  8 | import platform
  9 | import shutil
 10 | import subprocess
 11 | import threading
 12 | 
 13 | from overrides import override
 14 | 
 15 | from solidlsp.ls import SolidLanguageServer
 16 | from solidlsp.ls_config import LanguageServerConfig
 17 | from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
 18 | from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
 19 | from solidlsp.settings import SolidLSPSettings
 20 | 
 21 | log = logging.getLogger(__name__)
 22 | 
 23 | 
 24 | class ZigLanguageServer(SolidLanguageServer):
 25 |     """
 26 |     Provides Zig specific instantiation of the LanguageServer class using ZLS.
 27 |     """
 28 | 
 29 |     @override
 30 |     def is_ignored_dirname(self, dirname: str) -> bool:
 31 |         # For Zig projects, we should ignore:
 32 |         # - zig-cache: build cache directory
 33 |         # - zig-out: default build output directory
 34 |         # - .zig-cache: alternative cache location
 35 |         # - node_modules: if the project has JavaScript components
 36 |         return super().is_ignored_dirname(dirname) or dirname in ["zig-cache", "zig-out", ".zig-cache", "node_modules", "build", "dist"]
 37 | 
 38 |     @staticmethod
 39 |     def _get_zig_version() -> str | None:
 40 |         """Get the installed Zig version or None if not found."""
 41 |         try:
 42 |             result = subprocess.run(["zig", "version"], capture_output=True, text=True, check=False)
 43 |             if result.returncode == 0:
 44 |                 return result.stdout.strip()
 45 |         except FileNotFoundError:
 46 |             return None
 47 |         return None
 48 | 
 49 |     @staticmethod
 50 |     def _get_zls_version() -> str | None:
 51 |         """Get the installed ZLS version or None if not found."""
 52 |         try:
 53 |             result = subprocess.run(["zls", "--version"], capture_output=True, text=True, check=False)
 54 |             if result.returncode == 0:
 55 |                 return result.stdout.strip()
 56 |         except FileNotFoundError:
 57 |             return None
 58 |         return None
 59 | 
 60 |     @staticmethod
 61 |     def _check_zls_installed() -> bool:
 62 |         """Check if ZLS is installed in the system."""
 63 |         return shutil.which("zls") is not None
 64 | 
 65 |     @staticmethod
 66 |     def _setup_runtime_dependency() -> bool:
 67 |         """
 68 |         Check if required Zig runtime dependencies are available.
 69 |         Raises RuntimeError with helpful message if dependencies are missing.
 70 |         """
 71 |         # Check for Windows and provide error message
 72 |         if platform.system() == "Windows":
 73 |             raise RuntimeError(
 74 |                 "Windows is not supported by ZLS in this integration. "
 75 |                 "Cross-file references don't work reliably on Windows. Reason unknown."
 76 |             )
 77 | 
 78 |         zig_version = ZigLanguageServer._get_zig_version()
 79 |         if not zig_version:
 80 |             raise RuntimeError(
 81 |                 "Zig is not installed. Please install Zig from https://ziglang.org/download/ and make sure it is added to your PATH."
 82 |             )
 83 | 
 84 |         if not ZigLanguageServer._check_zls_installed():
 85 |             zls_version = ZigLanguageServer._get_zls_version()
 86 |             if not zls_version:
 87 |                 raise RuntimeError(
 88 |                     "Found Zig but ZLS (Zig Language Server) is not installed.\n"
 89 |                     "Please install ZLS from https://github.com/zigtools/zls\n"
 90 |                     "You can install it via:\n"
 91 |                     "  - Package managers (brew install zls, scoop install zls, etc.)\n"
 92 |                     "  - Download pre-built binaries from GitHub releases\n"
 93 |                     "  - Build from source with: zig build -Doptimize=ReleaseSafe\n\n"
 94 |                     "After installation, make sure 'zls' is added to your PATH."
 95 |                 )
 96 | 
 97 |         return True
 98 | 
 99 |     def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
100 |         self._setup_runtime_dependency()
101 | 
102 |         super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd="zls", cwd=repository_root_path), "zig", solidlsp_settings)
103 |         self.server_ready = threading.Event()
104 |         self.request_id = 0
105 | 
106 |     @staticmethod
107 |     def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
108 |         """
109 |         Returns the initialize params for the Zig Language Server.
110 |         """
111 |         root_uri = pathlib.Path(repository_absolute_path).as_uri()
112 |         initialize_params = {
113 |             "locale": "en",
114 |             "capabilities": {
115 |                 "textDocument": {
116 |                     "synchronization": {"didSave": True, "dynamicRegistration": True},
117 |                     "definition": {"dynamicRegistration": True},
118 |                     "references": {"dynamicRegistration": True},
119 |                     "documentSymbol": {
120 |                         "dynamicRegistration": True,
121 |                         "hierarchicalDocumentSymbolSupport": True,
122 |                         "symbolKind": {"valueSet": list(range(1, 27))},
123 |                     },
124 |                     "completion": {
125 |                         "dynamicRegistration": True,
126 |                         "completionItem": {
127 |                             "snippetSupport": True,
128 |                             "commitCharactersSupport": True,
129 |                             "documentationFormat": ["markdown", "plaintext"],
130 |                             "deprecatedSupport": True,
131 |                             "preselectSupport": True,
132 |                         },
133 |                     },
134 |                     "hover": {
135 |                         "dynamicRegistration": True,
136 |                         "contentFormat": ["markdown", "plaintext"],
137 |                     },
138 |                 },
139 |                 "workspace": {
140 |                     "workspaceFolders": True,
141 |                     "didChangeConfiguration": {"dynamicRegistration": True},
142 |                     "configuration": True,
143 |                 },
144 |             },
145 |             "processId": os.getpid(),
146 |             "rootPath": repository_absolute_path,
147 |             "rootUri": root_uri,
148 |             "workspaceFolders": [
149 |                 {
150 |                     "uri": root_uri,
151 |                     "name": os.path.basename(repository_absolute_path),
152 |                 }
153 |             ],
154 |             "initializationOptions": {
155 |                 # ZLS specific options based on schema.json
156 |                 # Critical paths for ZLS to understand the project
157 |                 "zig_exe_path": shutil.which("zig"),  # Path to zig executable
158 |                 "zig_lib_path": None,  # Let ZLS auto-detect
159 |                 "build_runner_path": None,  # Let ZLS use its built-in runner
160 |                 "global_cache_path": None,  # Let ZLS use default cache
161 |                 # Build configuration
162 |                 "enable_build_on_save": True,  # Enable to analyze project structure
163 |                 "build_on_save_args": ["build"],
164 |                 # Features
165 |                 "enable_snippets": True,
166 |                 "enable_argument_placeholders": True,
167 |                 "semantic_tokens": "full",
168 |                 "warn_style": False,
169 |                 "highlight_global_var_declarations": False,
170 |                 "skip_std_references": False,
171 |                 "prefer_ast_check_as_child_process": True,
172 |                 "completion_label_details": True,
173 |                 # Inlay hints configuration
174 |                 "inlay_hints_show_variable_type_hints": True,
175 |                 "inlay_hints_show_struct_literal_field_type": True,
176 |                 "inlay_hints_show_parameter_name": True,
177 |                 "inlay_hints_show_builtin": True,
178 |                 "inlay_hints_exclude_single_argument": True,
179 |                 "inlay_hints_hide_redundant_param_names": False,
180 |                 "inlay_hints_hide_redundant_param_names_last_token": False,
181 |             },
182 |         }
183 |         return initialize_params  # type: ignore[return-value]
184 | 
185 |     def _start_server(self) -> None:
186 |         """Start ZLS server process"""
187 | 
188 |         def register_capability_handler(params: dict) -> None:
189 |             return
190 | 
191 |         def window_log_message(msg: dict) -> None:
192 |             log.info(f"LSP: window/logMessage: {msg}")
193 | 
194 |         def do_nothing(params: dict) -> None:
195 |             return
196 | 
197 |         self.server.on_request("client/registerCapability", register_capability_handler)
198 |         self.server.on_notification("window/logMessage", window_log_message)
199 |         self.server.on_notification("$/progress", do_nothing)
200 |         self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
201 | 
202 |         log.info("Starting ZLS server process")
203 |         self.server.start()
204 |         initialize_params = self._get_initialize_params(self.repository_root_path)
205 | 
206 |         log.info("Sending initialize request from LSP client to LSP server and awaiting response")
207 |         init_response = self.server.send.initialize(initialize_params)
208 | 
209 |         # Verify server capabilities
210 |         assert "textDocumentSync" in init_response["capabilities"]
211 |         assert "definitionProvider" in init_response["capabilities"]
212 |         assert "documentSymbolProvider" in init_response["capabilities"]
213 |         assert "referencesProvider" in init_response["capabilities"]
214 | 
215 |         self.server.notify.initialized({})
216 |         self.completions_available.set()
217 | 
218 |         # ZLS server is ready after initialization
219 |         self.server_ready.set()
220 |         self.server_ready.wait()
221 | 
222 |         # Open build.zig if it exists to help ZLS understand project structure
223 |         build_zig_path = os.path.join(self.repository_root_path, "build.zig")
224 |         if os.path.exists(build_zig_path):
225 |             try:
226 |                 with open(build_zig_path, encoding="utf-8") as f:
227 |                     content = f.read()
228 |                     uri = pathlib.Path(build_zig_path).as_uri()
229 |                     self.server.notify.did_open_text_document(
230 |                         {
231 |                             "textDocument": {
232 |                                 "uri": uri,
233 |                                 "languageId": "zig",
234 |                                 "version": 1,
235 |                                 "text": content,
236 |                             }
237 |                         }
238 |                     )
239 |                     log.info("Opened build.zig to provide project context to ZLS")
240 |             except Exception as e:
241 |                 log.warning(f"Failed to open build.zig: {e}")
242 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/yaml_ls/test_yaml_basic.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Basic integration tests for the YAML language server functionality.
  3 | 
  4 | These tests validate the functionality of the language server APIs
  5 | like request_document_symbols using the YAML test repository.
  6 | """
  7 | 
  8 | from pathlib import Path
  9 | 
 10 | import pytest
 11 | 
 12 | from solidlsp import SolidLanguageServer
 13 | from solidlsp.ls_config import Language
 14 | 
 15 | 
 16 | @pytest.mark.yaml
 17 | class TestYAMLLanguageServerBasics:
 18 |     """Test basic functionality of the YAML language server."""
 19 | 
 20 |     @pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
 21 |     @pytest.mark.parametrize("repo_path", [Language.YAML], indirect=True)
 22 |     def test_yaml_language_server_initialization(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 23 |         """Test that YAML language server can be initialized successfully."""
 24 |         assert language_server is not None
 25 |         assert language_server.language == Language.YAML
 26 |         assert language_server.is_running()
 27 |         assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
 28 | 
 29 |     @pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
 30 |     @pytest.mark.parametrize("repo_path", [Language.YAML], indirect=True)
 31 |     def test_yaml_config_file_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 32 |         """Test document symbols detection in config.yaml with specific symbol verification."""
 33 |         all_symbols, root_symbols = language_server.request_document_symbols("config.yaml").get_all_symbols_and_roots()
 34 | 
 35 |         assert all_symbols is not None, "Should return symbols for config.yaml"
 36 |         assert len(all_symbols) > 0, f"Should find symbols in config.yaml, found {len(all_symbols)}"
 37 | 
 38 |         # Verify specific top-level keys are detected
 39 |         symbol_names = [sym.get("name") for sym in all_symbols]
 40 |         assert "app" in symbol_names, "Should detect 'app' key in config.yaml"
 41 |         assert "database" in symbol_names, "Should detect 'database' key in config.yaml"
 42 |         assert "logging" in symbol_names, "Should detect 'logging' key in config.yaml"
 43 |         assert "features" in symbol_names, "Should detect 'features' key in config.yaml"
 44 | 
 45 |         # Verify nested symbols exist (child keys under 'app')
 46 |         assert "name" in symbol_names, "Should detect nested 'name' key"
 47 |         assert "port" in symbol_names, "Should detect nested 'port' key"
 48 |         assert "debug" in symbol_names, "Should detect nested 'debug' key"
 49 | 
 50 |         # Check symbol kinds are appropriate (LSP kinds: 2=module/namespace, 15=string, 16=number, 17=boolean)
 51 |         app_symbol = next((s for s in all_symbols if s.get("name") == "app"), None)
 52 |         assert app_symbol is not None, "Should find 'app' symbol"
 53 |         assert app_symbol.get("kind") == 2, "Top-level object should have kind 2 (module/namespace)"
 54 | 
 55 |         port_symbol = next((s for s in all_symbols if s.get("name") == "port"), None)
 56 |         assert port_symbol is not None, "Should find 'port' symbol"
 57 |         assert port_symbol.get("kind") == 16, "'port' should have kind 16 (number)"
 58 | 
 59 |         debug_symbol = next((s for s in all_symbols if s.get("name") == "debug"), None)
 60 |         assert debug_symbol is not None, "Should find 'debug' symbol"
 61 |         assert debug_symbol.get("kind") == 17, "'debug' should have kind 17 (boolean)"
 62 | 
 63 |     @pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
 64 |     @pytest.mark.parametrize("repo_path", [Language.YAML], indirect=True)
 65 |     def test_yaml_services_file_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 66 |         """Test symbol detection in services.yml Docker Compose file."""
 67 |         all_symbols, root_symbols = language_server.request_document_symbols("services.yml").get_all_symbols_and_roots()
 68 | 
 69 |         assert all_symbols is not None, "Should return symbols for services.yml"
 70 |         assert len(all_symbols) > 0, f"Should find symbols in services.yml, found {len(all_symbols)}"
 71 | 
 72 |         # Verify specific top-level keys from Docker Compose file
 73 |         symbol_names = [sym.get("name") for sym in all_symbols]
 74 |         assert "version" in symbol_names, "Should detect 'version' key"
 75 |         assert "services" in symbol_names, "Should detect 'services' key"
 76 |         assert "networks" in symbol_names, "Should detect 'networks' key"
 77 |         assert "volumes" in symbol_names, "Should detect 'volumes' key"
 78 | 
 79 |         # Verify service names
 80 |         assert "web" in symbol_names, "Should detect 'web' service"
 81 |         assert "api" in symbol_names, "Should detect 'api' service"
 82 |         assert "database" in symbol_names, "Should detect 'database' service"
 83 | 
 84 |         # Check that arrays are properly detected
 85 |         ports_symbols = [s for s in all_symbols if s.get("name") == "ports"]
 86 |         assert len(ports_symbols) > 0, "Should find 'ports' arrays in services"
 87 |         # Arrays should have kind 18
 88 |         for ports_sym in ports_symbols:
 89 |             assert ports_sym.get("kind") == 18, "'ports' should have kind 18 (array)"
 90 | 
 91 |     @pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
 92 |     @pytest.mark.parametrize("repo_path", [Language.YAML], indirect=True)
 93 |     def test_yaml_data_file_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 94 |         """Test symbol detection in data.yaml file with array structures."""
 95 |         all_symbols, root_symbols = language_server.request_document_symbols("data.yaml").get_all_symbols_and_roots()
 96 | 
 97 |         assert all_symbols is not None, "Should return symbols for data.yaml"
 98 |         assert len(all_symbols) > 0, f"Should find symbols in data.yaml, found {len(all_symbols)}"
 99 | 
100 |         # Verify top-level keys
101 |         symbol_names = [sym.get("name") for sym in all_symbols]
102 |         assert "users" in symbol_names, "Should detect 'users' array"
103 |         assert "projects" in symbol_names, "Should detect 'projects' array"
104 | 
105 |         # Verify array elements (indexed by position)
106 |         # data.yaml has user entries and project entries
107 |         assert "id" in symbol_names, "Should detect 'id' fields in array elements"
108 |         assert "name" in symbol_names, "Should detect 'name' fields"
109 |         assert "email" in symbol_names, "Should detect 'email' fields"
110 |         assert "roles" in symbol_names, "Should detect 'roles' arrays"
111 | 
112 |     @pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
113 |     @pytest.mark.parametrize("repo_path", [Language.YAML], indirect=True)
114 |     def test_yaml_symbols_with_body(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
115 |         """Test request_document_symbols with body extraction."""
116 |         all_symbols, root_symbols = language_server.request_document_symbols("config.yaml").get_all_symbols_and_roots()
117 | 
118 |         assert all_symbols is not None, "Should return symbols for config.yaml"
119 |         assert len(all_symbols) > 0, "Should have symbols"
120 | 
121 |         # Find the 'app' symbol and verify its body
122 |         app_symbol = next((s for s in all_symbols if s.get("name") == "app"), None)
123 |         assert app_symbol is not None, "Should find 'app' symbol"
124 | 
125 |         # Check that body exists and contains expected content
126 |         assert "body" in app_symbol, "'app' symbol should have body"
127 |         app_body = app_symbol["body"]
128 |         assert "app:" in app_body, "Body should start with 'app:'"
129 |         assert "name: test-application" in app_body, "Body should contain 'name' field"
130 |         assert "version: 1.0.0" in app_body, "Body should contain 'version' field"
131 |         assert "port: 8080" in app_body, "Body should contain 'port' field"
132 |         assert "debug: true" in app_body, "Body should contain 'debug' field"
133 | 
134 |         # Find a simple string value symbol and verify its body
135 |         name_symbols = [s for s in all_symbols if s.get("name") == "name" and "body" in s]
136 |         assert len(name_symbols) > 0, "Should find 'name' symbols with bodies"
137 |         # At least one should contain "test-application"
138 |         assert any("test-application" in s["body"] for s in name_symbols), "Should find name with test-application"
139 | 
140 |         # Find the database symbol and check its body
141 |         database_symbol = next((s for s in all_symbols if s.get("name") == "database"), None)
142 |         assert database_symbol is not None, "Should find 'database' symbol"
143 |         assert "body" in database_symbol, "'database' symbol should have body"
144 |         db_body = database_symbol["body"]
145 |         assert "database:" in db_body, "Body should start with 'database:'"
146 |         assert "host: localhost" in db_body, "Body should contain host configuration"
147 |         assert "port: 5432" in db_body, "Body should contain port configuration"
148 | 
149 |     @pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
150 |     @pytest.mark.parametrize("repo_path", [Language.YAML], indirect=True)
151 |     def test_yaml_symbol_ranges(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
152 |         """Test that symbols have proper range information."""
153 |         all_symbols, root_symbols = language_server.request_document_symbols("config.yaml").get_all_symbols_and_roots()
154 | 
155 |         assert all_symbols is not None
156 |         assert len(all_symbols) > 0
157 | 
158 |         # Check the 'app' symbol range
159 |         app_symbol = next((s for s in all_symbols if s.get("name") == "app"), None)
160 |         assert app_symbol is not None, "Should find 'app' symbol"
161 |         assert "range" in app_symbol, "'app' symbol should have range"
162 | 
163 |         app_range = app_symbol["range"]
164 |         assert "start" in app_range, "Range should have start"
165 |         assert "end" in app_range, "Range should have end"
166 |         assert app_range["start"]["line"] == 1, "'app' should start at line 1 (0-indexed, actual line 2)"
167 |         # The app block spans from line 2 to line 7 in the file (1-indexed)
168 |         # In 0-indexed LSP coordinates: line 1 (start) to line 6 (end)
169 |         assert app_range["end"]["line"] == 6, "'app' should end at line 6 (0-indexed)"
170 | 
171 |         # Check a nested symbol range
172 |         port_symbols = [s for s in all_symbols if s.get("name") == "port"]
173 |         assert len(port_symbols) > 0, "Should find 'port' symbols"
174 |         # Find the one under 'app' (should be at line 4 in 0-indexed, actual line 5)
175 |         app_port = next((s for s in port_symbols if s["range"]["start"]["line"] == 4), None)
176 |         assert app_port is not None, "Should find 'port' under 'app'"
177 |         assert app_port["range"]["start"]["character"] == 2, "'port' should be indented 2 spaces"
178 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/haskell/test_haskell_basic.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Rigorous tests for Haskell Language Server integration with Serena.
  3 | 
  4 | Tests prove that Serena's symbol tools can:
  5 | 1. Discover all expected symbols with precise matching
  6 | 2. Track cross-file references accurately
  7 | 3. Identify data type structures and record fields
  8 | 4. Navigate between definitions and usages
  9 | 
 10 | Test Repository Structure:
 11 | - src/Calculator.hs: Calculator data type, arithmetic functions (add, subtract, multiply, divide, calculate)
 12 | - src/Helper.hs: Helper functions (validateNumber, isPositive, isNegative, absolute)
 13 | - app/Main.hs: Main entry point using Calculator and Helper modules
 14 | """
 15 | 
 16 | import sys
 17 | 
 18 | import pytest
 19 | 
 20 | from solidlsp.ls import SolidLanguageServer
 21 | from solidlsp.ls_config import Language
 22 | 
 23 | 
 24 | @pytest.mark.haskell
 25 | @pytest.mark.skipif(sys.platform == "win32", reason="HLS not installed on Windows CI")
 26 | class TestHaskellLanguageServer:
 27 |     @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
 28 |     def test_calculator_module_symbols(self, language_server: SolidLanguageServer):
 29 |         """
 30 |         Test precise symbol discovery in Calculator.hs.
 31 | 
 32 |         Verifies that Serena can identify:
 33 |         - Data type definition (Calculator with record fields)
 34 |         - All exported functions with correct names
 35 |         - Module structure
 36 |         """
 37 |         all_symbols, _ = language_server.request_document_symbols("src/Calculator.hs").get_all_symbols_and_roots()
 38 |         symbol_names = {s["name"] for s in all_symbols}
 39 | 
 40 |         # Verify exact set of expected top-level symbols
 41 |         expected_symbols = {
 42 |             "Calculator",  # Data type
 43 |             "add",  # Function: Int -> Int -> Int
 44 |             "subtract",  # Function: Int -> Int -> Int
 45 |             "multiply",  # Function: Int -> Int -> Int
 46 |             "divide",  # Function: Int -> Int -> Maybe Int
 47 |             "calculate",  # Function: Calculator -> String -> Int -> Int -> Maybe Int
 48 |         }
 49 | 
 50 |         # Verify all expected symbols are present
 51 |         missing = expected_symbols - symbol_names
 52 |         assert not missing, f"Missing expected symbols in Calculator.hs: {missing}"
 53 | 
 54 |         # Verify Calculator data type exists
 55 |         calculator_symbol = next((s for s in all_symbols if s["name"] == "Calculator"), None)
 56 |         assert calculator_symbol is not None, "Calculator data type not found"
 57 | 
 58 |         # The Calculator should be identified as a data type
 59 |         # HLS may use different SymbolKind values (1=File, 5=Class, 23=Struct)
 60 |         assert calculator_symbol["kind"] in [
 61 |             1,
 62 |             5,
 63 |             23,
 64 |         ], f"Calculator should be a data type (kind 1, 5, or 23), got kind {calculator_symbol['kind']}"
 65 | 
 66 |     @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
 67 |     def test_helper_module_symbols(self, language_server: SolidLanguageServer):
 68 |         """
 69 |         Test precise symbol discovery in Helper.hs.
 70 | 
 71 |         Verifies Serena identifies all helper functions that are imported
 72 |         and used by Calculator module.
 73 |         """
 74 |         all_symbols, _ = language_server.request_document_symbols("src/Helper.hs").get_all_symbols_and_roots()
 75 |         symbol_names = {s["name"] for s in all_symbols}
 76 | 
 77 |         # Verify expected helper functions (module name may also appear)
 78 |         expected_symbols = {
 79 |             "validateNumber",  # Function used by Calculator.add and Calculator.subtract
 80 |             "isPositive",  # Predicate function
 81 |             "isNegative",  # Predicate function used by absolute
 82 |             "absolute",  # Function that uses isNegative
 83 |         }
 84 | 
 85 |         # All expected symbols should be present (module name is optional)
 86 |         missing = expected_symbols - symbol_names
 87 |         assert not missing, f"Missing expected symbols in Helper.hs: {missing}"
 88 | 
 89 |         # Verify no unexpected symbols beyond the module name
 90 |         extra = symbol_names - expected_symbols - {"Helper"}
 91 |         assert not extra, f"Unexpected symbols in Helper.hs: {extra}"
 92 | 
 93 |     @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
 94 |     def test_main_module_imports(self, language_server: SolidLanguageServer):
 95 |         """
 96 |         Test that Main.hs properly references both Calculator and Helper modules.
 97 | 
 98 |         Verifies Serena can identify cross-module dependencies.
 99 |         """
100 |         all_symbols, _ = language_server.request_document_symbols("app/Main.hs").get_all_symbols_and_roots()
101 |         symbol_names = {s["name"] for s in all_symbols}
102 | 
103 |         # Main.hs should have the main function
104 |         assert "main" in symbol_names, "Main.hs should contain 'main' function"
105 | 
106 |     @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
107 |     def test_cross_file_references_validateNumber(self, language_server: SolidLanguageServer):
108 |         """
109 |         Test cross-file reference tracking for validateNumber function.
110 | 
111 |         validateNumber is defined in Helper.hs:9 and used in:
112 |         - Calculator.hs:21 (in add function)
113 |         - Calculator.hs:25 (in subtract function)
114 | 
115 |         This proves Serena can track function usage across module boundaries.
116 |         """
117 |         # Get references to validateNumber (defined at line 9, 0-indexed = line 8)
118 |         references = language_server.request_references("src/Helper.hs", line=8, column=0)
119 | 
120 |         # Should find at least: definition in Helper.hs + 2 usages in Calculator.hs
121 |         assert len(references) >= 2, f"Expected at least 2 references to validateNumber (used in add and subtract), got {len(references)}"
122 | 
123 |         # Verify we have references in Calculator.hs
124 |         reference_paths = [ref["relativePath"] for ref in references]
125 |         calculator_refs = [path for path in reference_paths if "Calculator.hs" in path]
126 | 
127 |         assert len(calculator_refs) >= 2, (
128 |             f"Expected at least 2 references in Calculator.hs (add and subtract functions), "
129 |             f"got {len(calculator_refs)} references in Calculator.hs"
130 |         )
131 | 
132 |     @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
133 |     def test_within_file_references_isNegative(self, language_server: SolidLanguageServer):
134 |         """
135 |         Test within-file reference tracking for isNegative function.
136 | 
137 |         isNegative is defined in Helper.hs:17 and used in Helper.hs:22 (absolute function).
138 |         This proves Serena can track intra-module function calls.
139 |         """
140 |         # isNegative defined at line 17 (0-indexed = line 16)
141 |         references = language_server.request_references("src/Helper.hs", line=16, column=0)
142 | 
143 |         # Should find: definition + usage in absolute function
144 |         assert len(references) >= 1, f"Expected at least 1 reference to isNegative (used in absolute), got {len(references)}"
145 | 
146 |         # All references should be in Helper.hs
147 |         reference_paths = [ref["relativePath"] for ref in references]
148 |         assert all(
149 |             "Helper.hs" in path for path in reference_paths
150 |         ), f"All isNegative references should be in Helper.hs, got: {reference_paths}"
151 | 
152 |     @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
153 |     def test_function_references_from_main(self, language_server: SolidLanguageServer):
154 |         """
155 |         Test that functions used in Main.hs can be traced back to their definitions.
156 | 
157 |         Main.hs:12 calls 'add' from Calculator module.
158 |         Main.hs:25 calls 'isPositive' from Helper module.
159 |         Main.hs:26 calls 'absolute' from Helper module.
160 | 
161 |         This proves Serena can track cross-module function calls from executable code.
162 |         """
163 |         # Test 'add' function references (defined in Calculator.hs:20, 0-indexed = line 19)
164 |         add_refs = language_server.request_references("src/Calculator.hs", line=19, column=0)
165 | 
166 |         # Should find references in Main.hs and possibly Calculator.hs (calculate function uses it)
167 |         assert len(add_refs) >= 1, f"Expected at least 1 reference to 'add', got {len(add_refs)}"
168 | 
169 |         add_ref_paths = [ref["relativePath"] for ref in add_refs]
170 |         # Should have at least one reference in Main.hs or Calculator.hs
171 |         assert any(
172 |             "Main.hs" in path or "Calculator.hs" in path for path in add_ref_paths
173 |         ), f"Expected 'add' to be referenced in Main.hs or Calculator.hs, got: {add_ref_paths}"
174 | 
175 |     @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
176 |     def test_multiply_function_usage_in_calculate(self, language_server: SolidLanguageServer):
177 |         """
178 |         Test that multiply function usage is tracked within Calculator module.
179 | 
180 |         multiply is defined in Calculator.hs:28 and used in:
181 |         - Calculator.hs:41 (in calculate function via pattern matching)
182 |         - Main.hs:20 (via calculate call with "multiply" operator)
183 | 
184 |         This proves Serena can track function references even when called indirectly.
185 |         """
186 |         # multiply defined at line 28 (0-indexed = line 27)
187 |         multiply_refs = language_server.request_references("src/Calculator.hs", line=27, column=0)
188 | 
189 |         # Should find at least the usage in calculate function
190 |         assert len(multiply_refs) >= 1, f"Expected at least 1 reference to 'multiply', got {len(multiply_refs)}"
191 | 
192 |         # Should have reference in Calculator.hs (calculate function)
193 |         multiply_ref_paths = [ref["relativePath"] for ref in multiply_refs]
194 |         assert any(
195 |             "Calculator.hs" in path for path in multiply_ref_paths
196 |         ), f"Expected 'multiply' to be referenced in Calculator.hs, got: {multiply_ref_paths}"
197 | 
198 |     @pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
199 |     def test_data_type_constructor_references(self, language_server: SolidLanguageServer):
200 |         """
201 |         Test that Calculator data type constructor usage is tracked.
202 | 
203 |         Calculator is defined in Calculator.hs:14 and used in:
204 |         - Main.hs:8 (constructor call: Calculator "TestCalc" 1)
205 |         - Calculator.hs:37 (type signature for calculate function)
206 | 
207 |         This proves Serena can track data type constructor references.
208 |         """
209 |         # Calculator data type defined at line 14 (0-indexed = line 13)
210 |         calculator_refs = language_server.request_references("src/Calculator.hs", line=13, column=5)
211 | 
212 |         # Should find usage in Main.hs
213 |         assert len(calculator_refs) >= 1, f"Expected at least 1 reference to Calculator constructor, got {len(calculator_refs)}"
214 | 
215 |         # Should have at least one reference in Main.hs or Calculator.hs
216 |         calc_ref_paths = [ref["relativePath"] for ref in calculator_refs]
217 |         assert any(
218 |             "Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths
219 |         ), f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}"
220 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/erlang/test_erlang_ignored_dirs.py:
--------------------------------------------------------------------------------

```python
  1 | from collections.abc import Generator
  2 | from pathlib import Path
  3 | 
  4 | import pytest
  5 | 
  6 | from solidlsp import SolidLanguageServer
  7 | from solidlsp.ls_config import Language
  8 | from test.conftest import start_ls_context
  9 | 
 10 | from . import ERLANG_LS_UNAVAILABLE, ERLANG_LS_UNAVAILABLE_REASON
 11 | 
 12 | # These marks will be applied to all tests in this module
 13 | pytestmark = [
 14 |     pytest.mark.erlang,
 15 |     pytest.mark.skipif(ERLANG_LS_UNAVAILABLE, reason=f"Erlang LS not available: {ERLANG_LS_UNAVAILABLE_REASON}"),
 16 | ]
 17 | 
 18 | 
 19 | @pytest.fixture(scope="module")
 20 | def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
 21 |     """Fixture to set up an LS for the erlang test repo with the 'ignored_dir' directory ignored."""
 22 |     ignored_paths = ["_build", "ignored_dir"]
 23 |     with start_ls_context(language=Language.ERLANG, ignored_paths=ignored_paths) as ls:
 24 |         yield ls
 25 | 
 26 | 
 27 | @pytest.mark.timeout(60)  # Add 60 second timeout
 28 | @pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
 29 | @pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True)
 30 | def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
 31 |     """Tests that request_full_symbol_tree ignores the configured directory."""
 32 |     root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
 33 |     root_children = root["children"]
 34 |     children_names = {child["name"] for child in root_children}
 35 | 
 36 |     # Should have src, include, and test directories, but not _build or ignored_dir
 37 |     expected_dirs = {"src", "include", "test"}
 38 |     found_expected = expected_dirs.intersection(children_names)
 39 |     assert len(found_expected) > 0, f"Expected some dirs from {expected_dirs} to be in {children_names}"
 40 |     assert "_build" not in children_names, f"_build should not be in {children_names}"
 41 |     assert "ignored_dir" not in children_names, f"ignored_dir should not be in {children_names}"
 42 | 
 43 | 
 44 | @pytest.mark.timeout(60)  # Add 60 second timeout
 45 | @pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
 46 | @pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True)
 47 | def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
 48 |     """Tests that find_references ignores the configured directory."""
 49 |     # Location of user record, which might be referenced in ignored_dir
 50 |     definition_file = "include/records.hrl"
 51 | 
 52 |     # Find the user record definition
 53 |     symbols = ls_with_ignored_dirs.request_document_symbols(definition_file).get_all_symbols_and_roots()
 54 |     user_symbol = None
 55 |     for symbol_group in symbols:
 56 |         user_symbol = next((s for s in symbol_group if "user" in s.get("name", "").lower()), None)
 57 |         if user_symbol:
 58 |             break
 59 | 
 60 |     if not user_symbol or "selectionRange" not in user_symbol:
 61 |         pytest.skip("User record symbol not found for reference testing")
 62 | 
 63 |     sel_start = user_symbol["selectionRange"]["start"]
 64 |     references = ls_with_ignored_dirs.request_references(definition_file, sel_start["line"], sel_start["character"])
 65 | 
 66 |     # Assert that _build and ignored_dir do not appear in the references
 67 |     assert not any("_build" in ref["relativePath"] for ref in references), "_build should be ignored"
 68 |     assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored"
 69 | 
 70 | 
 71 | @pytest.mark.timeout(60)  # Add 60 second timeout
 72 | @pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
 73 | @pytest.mark.parametrize("repo_path", [Language.ERLANG], indirect=True)
 74 | def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
 75 |     """Tests that refs and symbols with glob patterns are ignored."""
 76 |     ignored_paths = ["_build*", "ignored_*", "*.tmp"]
 77 |     with start_ls_context(language=Language.ERLANG, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
 78 |         # Same as in the above tests
 79 |         root = ls.request_full_symbol_tree()[0]
 80 |         root_children = root["children"]
 81 |         children_names = {child["name"] for child in root_children}
 82 | 
 83 |         # Should have src, include, and test directories, but not _build or ignored_dir
 84 |         expected_dirs = {"src", "include", "test"}
 85 |         found_expected = expected_dirs.intersection(children_names)
 86 |         assert len(found_expected) > 0, f"Expected some dirs from {expected_dirs} to be in {children_names}"
 87 |         assert "_build" not in children_names, f"_build should not be in {children_names} (glob pattern)"
 88 |         assert "ignored_dir" not in children_names, f"ignored_dir should not be in {children_names} (glob pattern)"
 89 | 
 90 |         # Test that the refs and symbols with glob patterns are ignored
 91 |         definition_file = "include/records.hrl"
 92 | 
 93 |         # Find the user record definition
 94 |         symbols = ls.request_document_symbols(definition_file).get_all_symbols_and_roots()
 95 |         user_symbol = None
 96 |         for symbol_group in symbols:
 97 |             user_symbol = next((s for s in symbol_group if "user" in s.get("name", "").lower()), None)
 98 |             if user_symbol:
 99 |                 break
100 | 
101 |         if user_symbol and "selectionRange" in user_symbol:
102 |             sel_start = user_symbol["selectionRange"]["start"]
103 |             references = ls.request_references(definition_file, sel_start["line"], sel_start["character"])
104 | 
105 |             # Assert that _build and ignored_dir do not appear in references
106 |             assert not any("_build" in ref["relativePath"] for ref in references), "_build should be ignored (glob)"
107 |             assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored (glob)"
108 | 
109 | 
110 | @pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
111 | def test_default_ignored_directories(language_server: SolidLanguageServer):
112 |     """Test that default Erlang directories are ignored."""
113 |     # Test that Erlang-specific directories are ignored by default
114 |     assert language_server.is_ignored_dirname("_build"), "_build should be ignored"
115 |     assert language_server.is_ignored_dirname("ebin"), "ebin should be ignored"
116 |     assert language_server.is_ignored_dirname("deps"), "deps should be ignored"
117 |     assert language_server.is_ignored_dirname(".rebar3"), ".rebar3 should be ignored"
118 |     assert language_server.is_ignored_dirname("_checkouts"), "_checkouts should be ignored"
119 |     assert language_server.is_ignored_dirname("node_modules"), "node_modules should be ignored"
120 | 
121 |     # Test that important directories are not ignored
122 |     assert not language_server.is_ignored_dirname("src"), "src should not be ignored"
123 |     assert not language_server.is_ignored_dirname("include"), "include should not be ignored"
124 |     assert not language_server.is_ignored_dirname("test"), "test should not be ignored"
125 |     assert not language_server.is_ignored_dirname("priv"), "priv should not be ignored"
126 | 
127 | 
128 | @pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
129 | def test_symbol_tree_excludes_build_dirs(language_server: SolidLanguageServer):
130 |     """Test that symbol tree excludes build and dependency directories."""
131 |     symbol_tree = language_server.request_full_symbol_tree()
132 | 
133 |     if symbol_tree:
134 |         root = symbol_tree[0]
135 |         children_names = {child["name"] for child in root.get("children", [])}
136 | 
137 |         # Build and dependency directories should not appear
138 |         ignored_dirs = {"_build", "ebin", "deps", ".rebar3", "_checkouts", "node_modules"}
139 |         found_ignored = ignored_dirs.intersection(children_names)
140 |         assert len(found_ignored) == 0, f"Found ignored directories in symbol tree: {found_ignored}"
141 | 
142 |         # Important directories should appear
143 |         important_dirs = {"src", "include", "test"}
144 |         found_important = important_dirs.intersection(children_names)
145 |         assert len(found_important) > 0, f"Expected to find important directories: {important_dirs}, got: {children_names}"
146 | 
147 | 
148 | @pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
149 | def test_ignore_compiled_files(language_server: SolidLanguageServer):
150 |     """Test that compiled Erlang files are ignored."""
151 |     # Test that beam files are ignored
152 |     assert language_server.is_ignored_filename("module.beam"), "BEAM files should be ignored"
153 |     assert language_server.is_ignored_filename("app.beam"), "BEAM files should be ignored"
154 | 
155 |     # Test that source files are not ignored
156 |     assert not language_server.is_ignored_filename("module.erl"), "Erlang source files should not be ignored"
157 |     assert not language_server.is_ignored_filename("records.hrl"), "Header files should not be ignored"
158 | 
159 | 
160 | @pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
161 | def test_rebar_directories_ignored(language_server: SolidLanguageServer):
162 |     """Test that rebar-specific directories are ignored."""
163 |     # Test rebar3-specific directories
164 |     assert language_server.is_ignored_dirname("_build"), "rebar3 _build should be ignored"
165 |     assert language_server.is_ignored_dirname("_checkouts"), "rebar3 _checkouts should be ignored"
166 |     assert language_server.is_ignored_dirname(".rebar3"), "rebar3 cache should be ignored"
167 | 
168 |     # Test that rebar.lock and rebar.config are not ignored (they are configuration files)
169 |     assert not language_server.is_ignored_filename("rebar.config"), "rebar.config should not be ignored"
170 |     assert not language_server.is_ignored_filename("rebar.lock"), "rebar.lock should not be ignored"
171 | 
172 | 
173 | @pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True)
174 | def test_document_symbols_ignores_dirs(ls_with_ignored_dirs: SolidLanguageServer):
175 |     """Test that document symbols from ignored directories are not included."""
176 |     # Try to get symbols from a file in ignored directory (should not find it)
177 |     try:
178 |         ignored_file = "ignored_dir/ignored_module.erl"
179 |         symbols = ls_with_ignored_dirs.request_document_symbols(ignored_file).get_all_symbols_and_roots()
180 |         # If we get here, the file was found - symbols should be empty or None
181 |         if symbols:
182 |             assert len(symbols) == 0, "Should not find symbols in ignored directory"
183 |     except Exception:
184 |         # This is expected - the file should not be accessible
185 |         pass
186 | 
187 | 
188 | @pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
189 | def test_erlang_specific_ignore_patterns(language_server: SolidLanguageServer):
190 |     """Test Erlang-specific ignore patterns work correctly."""
191 |     erlang_ignored_dirs = ["_build", "ebin", ".rebar3", "_checkouts", "cover"]
192 | 
193 |     # These should be ignored
194 |     for dirname in erlang_ignored_dirs:
195 |         assert language_server.is_ignored_dirname(dirname), f"{dirname} should be ignored"
196 | 
197 |     # These should not be ignored
198 |     erlang_important_dirs = ["src", "include", "test", "priv"]
199 |     for dirname in erlang_important_dirs:
200 |         assert not language_server.is_ignored_dirname(dirname), f"{dirname} should not be ignored"
201 | 
```
Page 6/21FirstPrevNextLast