#
tokens: 42806/50000 5/296 files (page 9/12)
lines: off (toggle) GitHub
raw markdown copy
This is page 9 of 12. Use http://codebase.md/oraios/serena?lines=false&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
│       ├── junie.yml
│       ├── lint_and_docs.yaml
│       ├── publish.yml
│       └── pytest.yml
├── .gitignore
├── .serena
│   ├── 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
│   ├── custom_agent.md
│   └── serena_on_chatgpt.md
├── flake.lock
├── flake.nix
├── lessons_learned.md
├── LICENSE
├── llms-install.md
├── public
│   └── .gitignore
├── pyproject.toml
├── README.md
├── resources
│   ├── 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
├── 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
│   │   ├── mcp.py
│   │   ├── project.py
│   │   ├── prompt_factory.py
│   │   ├── resources
│   │   │   ├── config
│   │   │   │   ├── contexts
│   │   │   │   │   ├── agent.yml
│   │   │   │   │   ├── chatgpt.yml
│   │   │   │   │   ├── codex.yml
│   │   │   │   │   ├── context.template.yml
│   │   │   │   │   ├── desktop-app.yml
│   │   │   │   │   ├── ide-assistant.yml
│   │   │   │   │   └── oaicompat-agent.yml
│   │   │   │   ├── internal_modes
│   │   │   │   │   └── jetbrains.yml
│   │   │   │   ├── modes
│   │   │   │   │   ├── editing.yml
│   │   │   │   │   ├── interactive.yml
│   │   │   │   │   ├── mode.template.yml
│   │   │   │   │   ├── no-onboarding.yml
│   │   │   │   │   ├── onboarding.yml
│   │   │   │   │   ├── one-shot.yml
│   │   │   │   │   └── planning.yml
│   │   │   │   └── prompt_templates
│   │   │   │       ├── simple_tool_outputs.yml
│   │   │   │       └── system_prompt.yml
│   │   │   ├── dashboard
│   │   │   │   ├── dashboard.js
│   │   │   │   ├── index.html
│   │   │   │   ├── jquery.min.js
│   │   │   │   ├── serena-icon-16.png
│   │   │   │   ├── serena-icon-32.png
│   │   │   │   ├── serena-icon-48.png
│   │   │   │   ├── serena-logs-dark-mode.png
│   │   │   │   └── serena-logs.png
│   │   │   ├── project.template.yml
│   │   │   └── serena_config.template.yml
│   │   ├── symbol.py
│   │   ├── text_utils.py
│   │   ├── tools
│   │   │   ├── __init__.py
│   │   │   ├── cmd_tools.py
│   │   │   ├── config_tools.py
│   │   │   ├── file_tools.py
│   │   │   ├── jetbrains_plugin_client.py
│   │   │   ├── jetbrains_tools.py
│   │   │   ├── memory_tools.py
│   │   │   ├── symbol_tools.py
│   │   │   ├── tools_base.py
│   │   │   └── workflow_tools.py
│   │   └── util
│   │       ├── class_decorators.py
│   │       ├── exception.py
│   │       ├── file_system.py
│   │       ├── general.py
│   │       ├── git.py
│   │       ├── inspection.py
│   │       ├── logging.py
│   │       ├── shell.py
│   │       └── thread.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
│       │   ├── gopls.py
│       │   ├── intelephense.py
│       │   ├── jedi_server.py
│       │   ├── kotlin_language_server.py
│       │   ├── lua_ls.py
│       │   ├── marksman.py
│       │   ├── nixd_ls.py
│       │   ├── omnisharp
│       │   │   ├── initialize_params.json
│       │   │   ├── runtime_dependencies.json
│       │   │   └── workspace_did_change_configuration.json
│       │   ├── omnisharp.py
│       │   ├── perl_language_server.py
│       │   ├── pyright_server.py
│       │   ├── r_language_server.py
│       │   ├── regal_server.py
│       │   ├── ruby_lsp.py
│       │   ├── rust_analyzer.py
│       │   ├── solargraph.py
│       │   ├── sourcekit_lsp.py
│       │   ├── terraform_ls.py
│       │   ├── typescript_language_server.py
│       │   ├── vts_language_server.py
│       │   └── zls.py
│       ├── ls_config.py
│       ├── ls_exceptions.py
│       ├── ls_handler.py
│       ├── ls_logger.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
│           ├── subprocess_util.py
│           └── zip.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
│   │       ├── go
│   │       │   └── test_repo
│   │       │       └── main.go
│   │       ├── java
│   │       │   └── test_repo
│   │       │       ├── pom.xml
│   │       │       └── src
│   │       │           └── main
│   │       │               └── java
│   │       │                   └── test_repo
│   │       │                       ├── Main.java
│   │       │                       ├── Model.java
│   │       │                       ├── ModelUser.java
│   │       │                       └── Utils.java
│   │       ├── 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
│   │       ├── nix
│   │       │   └── test_repo
│   │       │       ├── .gitignore
│   │       │       ├── default.nix
│   │       │       ├── flake.nix
│   │       │       ├── lib
│   │       │       │   └── utils.nix
│   │       │       ├── modules
│   │       │       │   └── example.nix
│   │       │       └── scripts
│   │       │           └── hello.sh
│   │       ├── perl
│   │       │   └── test_repo
│   │       │       ├── helper.pl
│   │       │       └── main.pl
│   │       ├── php
│   │       │   └── test_repo
│   │       │       ├── helper.php
│   │       │       ├── index.php
│   │       │       └── simple_var.php
│   │       ├── 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
│   │       ├── swift
│   │       │   └── test_repo
│   │       │       ├── Package.swift
│   │       │       └── src
│   │       │           ├── main.swift
│   │       │           └── utils.swift
│   │       ├── terraform
│   │       │   └── test_repo
│   │       │       ├── data.tf
│   │       │       ├── main.tf
│   │       │       ├── outputs.tf
│   │       │       └── variables.tf
│   │       ├── typescript
│   │       │   └── test_repo
│   │       │       ├── .serena
│   │       │       │   └── project.yml
│   │       │       ├── index.ts
│   │       │       ├── tsconfig.json
│   │       │       └── use_helper.ts
│   │       └── 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_edit_marker.py
│   │   ├── test_mcp.py
│   │   ├── test_serena_agent.py
│   │   ├── test_symbol_editing.py
│   │   ├── test_symbol.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
│       ├── go
│       │   └── test_go_basic.py
│       ├── java
│       │   └── test_java_basic.py
│       ├── kotlin
│       │   └── test_kotlin_basic.py
│       ├── lua
│       │   └── test_lua_basic.py
│       ├── markdown
│       │   ├── __init__.py
│       │   └── test_markdown_basic.py
│       ├── nix
│       │   └── test_nix_basic.py
│       ├── perl
│       │   └── test_perl_basic.py
│       ├── php
│       │   └── test_php_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_basic.py
│       ├── swift
│       │   └── test_swift_basic.py
│       ├── terraform
│       │   └── test_terraform_basic.py
│       ├── typescript
│       │   └── test_typescript_basic.py
│       ├── util
│       │   └── test_zip.py
│       └── zig
│           └── test_zig_basic.py
└── uv.lock
```

# Files

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

```python
"""
CSharp Language Server using Microsoft.CodeAnalysis.LanguageServer (Official Roslyn-based LSP server)
"""

import json
import logging
import os
import platform
import shutil
import subprocess
import tarfile
import threading
import urllib.request
import zipfile
from pathlib import Path
from typing import Any, cast

from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PathUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from solidlsp.util.zip import SafeZipExtractor

from .common import RuntimeDependency, RuntimeDependencyCollection

_RUNTIME_DEPENDENCIES = [
    RuntimeDependency(
        id="CSharpLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for Windows (x64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.win-x64",
        package_version="5.0.0-1.25329.6",
        platform_id="win-x64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/win-x64",
    ),
    RuntimeDependency(
        id="CSharpLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for Windows (ARM64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.win-arm64",
        package_version="5.0.0-1.25329.6",
        platform_id="win-arm64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/win-arm64",
    ),
    RuntimeDependency(
        id="CSharpLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for macOS (x64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.osx-x64",
        package_version="5.0.0-1.25329.6",
        platform_id="osx-x64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/osx-x64",
    ),
    RuntimeDependency(
        id="CSharpLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for macOS (ARM64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.osx-arm64",
        package_version="5.0.0-1.25329.6",
        platform_id="osx-arm64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/osx-arm64",
    ),
    RuntimeDependency(
        id="CSharpLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for Linux (x64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.linux-x64",
        package_version="5.0.0-1.25329.6",
        platform_id="linux-x64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/linux-x64",
    ),
    RuntimeDependency(
        id="CSharpLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for Linux (ARM64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.linux-arm64",
        package_version="5.0.0-1.25329.6",
        platform_id="linux-arm64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/linux-arm64",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 9 Runtime for Windows (x64)",
        url="https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-win-x64.zip",
        platform_id="win-x64",
        archive_type="zip",
        binary_name="dotnet.exe",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 9 Runtime for Linux (x64)",
        url="https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-linux-x64.tar.gz",
        platform_id="linux-x64",
        archive_type="tar.gz",
        binary_name="dotnet",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 9 Runtime for Linux (ARM64)",
        url="https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-linux-arm64.tar.gz",
        platform_id="linux-arm64",
        archive_type="tar.gz",
        binary_name="dotnet",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 9 Runtime for macOS (x64)",
        url="https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-osx-x64.tar.gz",
        platform_id="osx-x64",
        archive_type="tar.gz",
        binary_name="dotnet",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 9 Runtime for macOS (ARM64)",
        url="https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-osx-arm64.tar.gz",
        platform_id="osx-arm64",
        archive_type="tar.gz",
        binary_name="dotnet",
    ),
]


def breadth_first_file_scan(root_dir):
    """
    Perform a breadth-first scan of files in the given directory.
    Yields file paths in breadth-first order.
    """
    queue = [root_dir]
    while queue:
        current_dir = queue.pop(0)
        try:
            for item in os.listdir(current_dir):
                if item.startswith("."):
                    continue
                item_path = os.path.join(current_dir, item)
                if os.path.isdir(item_path):
                    queue.append(item_path)
                elif os.path.isfile(item_path):
                    yield item_path
        except (PermissionError, OSError):
            # Skip directories we can't access
            pass


def find_solution_or_project_file(root_dir) -> str | None:
    """
    Find the first .sln file in breadth-first order.
    If no .sln file is found, look for a .csproj file.
    """
    sln_file = None
    csproj_file = None

    for filename in breadth_first_file_scan(root_dir):
        if filename.endswith(".sln") and sln_file is None:
            sln_file = filename
        elif filename.endswith(".csproj") and csproj_file is None:
            csproj_file = filename

        # If we found a .sln file, return it immediately
        if sln_file:
            return sln_file

    # If no .sln file was found, return the first .csproj file
    return csproj_file


class CSharpLanguageServer(SolidLanguageServer):
    """
    Provides C# specific instantiation of the LanguageServer class using `Microsoft.CodeAnalysis.LanguageServer`,
    the official Roslyn-based language server from Microsoft.

    You can pass a list of runtime dependency overrides in ls_specific_settings["csharp"]. This is a list of
    dicts, each containing at least the "id" key, and optionally "platform_id" to uniquely identify the dependency to override.
    For example, to override the URL of the .NET runtime on windows-x64, add the entry:

    ```
        {
            "id": "DotNetRuntime",
            "platform_id": "win-x64",
            "url": "https://example.com/custom-dotnet-runtime.zip"
        }
    ```

    See the `_RUNTIME_DEPENDENCIES` variable above for the available dependency ids and platform_ids.
    """

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        """
        Creates a CSharpLanguageServer instance. This class is not meant to be instantiated directly.
        Use LanguageServer.create() instead.
        """
        dotnet_path, language_server_path = self._ensure_server_installed(logger, config, solidlsp_settings)

        # Find solution or project file
        solution_or_project = find_solution_or_project_file(repository_root_path)

        # Create log directory
        log_dir = Path(self.ls_resources_dir(solidlsp_settings)) / "logs"
        log_dir.mkdir(parents=True, exist_ok=True)

        # Build command using dotnet directly
        cmd = [dotnet_path, language_server_path, "--logLevel=Information", f"--extensionLogDirectory={log_dir}", "--stdio"]

        # The language server will discover the solution/project from the workspace root
        if solution_or_project:
            logger.log(f"Found solution/project file: {solution_or_project}", logging.INFO)
        else:
            logger.log("No .sln or .csproj file found, language server will attempt auto-discovery", logging.WARNING)

        logger.log(f"Language server command: {' '.join(cmd)}", logging.DEBUG)

        super().__init__(
            config,
            logger,
            repository_root_path,
            ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
            "csharp",
            solidlsp_settings,
        )

        self.initialization_complete = threading.Event()

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return super().is_ignored_dirname(dirname) or dirname in ["bin", "obj", "packages", ".vs"]

    @classmethod
    def _ensure_server_installed(
        cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
    ) -> tuple[str, str]:
        """
        Ensure .NET runtime and Microsoft.CodeAnalysis.LanguageServer are available.
        Returns a tuple of (dotnet_path, language_server_dll_path).
        """
        language_enum = cls.get_language_enum_instance()
        ls_specific_settings = solidlsp_settings.ls_specific_settings or {}
        language_specific_config = ls_specific_settings.get(language_enum, {})
        runtime_dependency_overrides = cast(list[dict[str, Any]], language_specific_config.get("runtime_dependencies", []))

        logger.log("Resolving runtime dependencies", logging.DEBUG)
        if language_specific_config:
            logger.log(
                f"Language-specific config for {language_enum}: {language_specific_config}",
                logging.DEBUG,
            )

        runtime_dependencies = RuntimeDependencyCollection(
            _RUNTIME_DEPENDENCIES,
            overrides=runtime_dependency_overrides,
        )

        logger.log(
            f"Available runtime dependencies: {runtime_dependencies.get_dependencies_for_current_platform}",
            logging.DEBUG,
        )

        # Find the dependencies for our platform
        lang_server_dep = runtime_dependencies.get_single_dep_for_current_platform("CSharpLanguageServer")
        dotnet_runtime_dep = runtime_dependencies.get_single_dep_for_current_platform("DotNetRuntime")
        dotnet_path = CSharpLanguageServer._ensure_dotnet_runtime(logger, dotnet_runtime_dep, solidlsp_settings)
        server_dll_path = CSharpLanguageServer._ensure_language_server(logger, lang_server_dep, solidlsp_settings)

        return dotnet_path, server_dll_path

    @classmethod
    def _ensure_dotnet_runtime(
        cls, logger: LanguageServerLogger, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
    ) -> str:
        """Ensure .NET runtime is available and return the dotnet executable path."""
        # TODO: use RuntimeDependency util methods instead of custom validation/download logic

        # Check if dotnet is already available on the system
        system_dotnet = shutil.which("dotnet")
        if system_dotnet:
            # Check if it's .NET 9
            try:
                result = subprocess.run([system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
                if "Microsoft.NETCore.App 9." in result.stdout:
                    logger.log("Found system .NET 9 runtime", logging.INFO)
                    return system_dotnet
            except subprocess.CalledProcessError:
                pass

        # Download .NET 9 runtime using config
        return cls._ensure_dotnet_runtime_from_config(logger, dotnet_runtime_dep, solidlsp_settings)

    @classmethod
    def _ensure_language_server(
        cls, logger: LanguageServerLogger, lang_server_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
    ) -> str:
        """Ensure language server is available and return the DLL path."""
        package_name = lang_server_dep.package_name
        package_version = lang_server_dep.package_version

        server_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / f"{package_name}.{package_version}"
        server_dll = server_dir / lang_server_dep.binary_name

        if server_dll.exists():
            logger.log(f"Using cached Microsoft.CodeAnalysis.LanguageServer from {server_dll}", logging.INFO)
            return str(server_dll)

        # Download and install the language server
        logger.log(f"Downloading {package_name} version {package_version}...", logging.INFO)
        package_path = cls._download_nuget_package_direct(logger, package_name, package_version, solidlsp_settings)

        # Extract and install
        cls._extract_language_server(lang_server_dep, package_path, server_dir)

        if not server_dll.exists():
            raise SolidLSPException("Microsoft.CodeAnalysis.LanguageServer.dll not found after extraction")

        # Make executable on Unix systems
        if platform.system().lower() != "windows":
            server_dll.chmod(0o755)

        logger.log(f"Successfully installed Microsoft.CodeAnalysis.LanguageServer to {server_dll}", logging.INFO)
        return str(server_dll)

    @staticmethod
    def _extract_language_server(lang_server_dep: RuntimeDependency, package_path: Path, server_dir: Path) -> None:
        """Extract language server files from downloaded package."""
        extract_path = lang_server_dep.extract_path or "lib/net9.0"
        source_dir = package_path / extract_path

        if not source_dir.exists():
            # Try alternative locations
            for possible_dir in [
                package_path / "tools" / "net9.0" / "any",
                package_path / "lib" / "net9.0",
                package_path / "contentFiles" / "any" / "net9.0",
            ]:
                if possible_dir.exists():
                    source_dir = possible_dir
                    break
            else:
                raise SolidLSPException(f"Could not find language server files in package. Searched in {package_path}")

        # Copy files to cache directory
        server_dir.mkdir(parents=True, exist_ok=True)
        shutil.copytree(source_dir, server_dir, dirs_exist_ok=True)

    @classmethod
    def _download_nuget_package_direct(
        cls, logger: LanguageServerLogger, package_name: str, package_version: str, solidlsp_settings: SolidLSPSettings
    ) -> Path:
        """
        Download a NuGet package directly from the Azure NuGet feed.
        Returns the path to the extracted package directory.
        """
        azure_feed_url = "https://pkgs.dev.azure.com/azure-public/vside/_packaging/vs-impl/nuget/v3/index.json"

        # Create temporary directory for package download
        temp_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "temp_downloads"
        temp_dir.mkdir(parents=True, exist_ok=True)

        try:
            # First, get the service index from the Azure feed
            logger.log("Fetching NuGet service index from Azure feed...", logging.DEBUG)
            with urllib.request.urlopen(azure_feed_url) as response:
                service_index = json.loads(response.read().decode())

            # Find the package base address (for downloading packages)
            package_base_address = None
            for resource in service_index.get("resources", []):
                if resource.get("@type") == "PackageBaseAddress/3.0.0":
                    package_base_address = resource.get("@id")
                    break

            if not package_base_address:
                raise SolidLSPException("Could not find package base address in Azure NuGet feed")

            # Construct the download URL for the specific package
            package_id_lower = package_name.lower()
            package_version_lower = package_version.lower()
            package_url = f"{package_base_address.rstrip('/')}/{package_id_lower}/{package_version_lower}/{package_id_lower}.{package_version_lower}.nupkg"

            logger.log(f"Downloading package from: {package_url}", logging.DEBUG)

            # Download the .nupkg file
            nupkg_file = temp_dir / f"{package_name}.{package_version}.nupkg"
            urllib.request.urlretrieve(package_url, nupkg_file)

            # Extract the .nupkg file (it's just a zip file)
            package_extract_dir = temp_dir / f"{package_name}.{package_version}"
            package_extract_dir.mkdir(exist_ok=True)

            # Use SafeZipExtractor to handle long paths and skip errors
            extractor = SafeZipExtractor(archive_path=nupkg_file, extract_dir=package_extract_dir, verbose=False)
            extractor.extract_all()

            # Clean up the nupkg file
            nupkg_file.unlink()

            logger.log(f"Successfully downloaded and extracted {package_name} version {package_version}", logging.INFO)
            return package_extract_dir

        except Exception as e:
            raise SolidLSPException(
                f"Failed to download package {package_name} version {package_version} from Azure NuGet feed: {e}"
            ) from e

    @classmethod
    def _ensure_dotnet_runtime_from_config(
        cls, logger: LanguageServerLogger, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
    ) -> str:
        """
        Ensure .NET 9 runtime is available using runtime dependency configuration.
        Returns the path to the dotnet executable.
        """
        # TODO: use RuntimeDependency util methods instead of custom download logic

        # Check if dotnet is already available on the system
        system_dotnet = shutil.which("dotnet")
        if system_dotnet:
            # Check if it's .NET 9
            try:
                result = subprocess.run([system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
                if "Microsoft.NETCore.App 9." in result.stdout:
                    logger.log("Found system .NET 9 runtime", logging.INFO)
                    return system_dotnet
            except subprocess.CalledProcessError:
                pass

        # Download .NET 9 runtime using config
        dotnet_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "dotnet-runtime-9.0"
        dotnet_exe = dotnet_dir / dotnet_runtime_dep.binary_name

        if dotnet_exe.exists():
            logger.log(f"Using cached .NET runtime from {dotnet_exe}", logging.INFO)
            return str(dotnet_exe)

        # Download .NET runtime
        logger.log("Downloading .NET 9 runtime...", logging.INFO)
        dotnet_dir.mkdir(parents=True, exist_ok=True)

        custom_dotnet_runtime_url = solidlsp_settings.ls_specific_settings.get(cls.get_language_enum_instance(), {}).get(
            "dotnet_runtime_url"
        )
        if custom_dotnet_runtime_url is not None:
            logger.log(f"Using custom .NET runtime url: {custom_dotnet_runtime_url}", logging.INFO)
            url = custom_dotnet_runtime_url
        else:
            url = dotnet_runtime_dep.url

        archive_type = dotnet_runtime_dep.archive_type

        # Download the runtime
        download_path = dotnet_dir / f"dotnet-runtime.{archive_type}"
        try:
            logger.log(f"Downloading from {url}", logging.DEBUG)
            urllib.request.urlretrieve(url, download_path)

            # Extract the archive
            if archive_type == "zip":
                with zipfile.ZipFile(download_path, "r") as zip_ref:
                    zip_ref.extractall(dotnet_dir)
            else:
                # tar.gz
                with tarfile.open(download_path, "r:gz") as tar_ref:
                    tar_ref.extractall(dotnet_dir)

            # Remove the archive
            download_path.unlink()

            # Make dotnet executable on Unix
            if platform.system().lower() != "windows":
                dotnet_exe.chmod(0o755)

            logger.log(f"Successfully installed .NET 9 runtime to {dotnet_exe}", logging.INFO)
            return str(dotnet_exe)

        except Exception as e:
            raise SolidLSPException(f"Failed to download .NET 9 runtime from {url}: {e}") from e

    def _get_initialize_params(self) -> InitializeParams:
        """
        Returns the initialize params for the Microsoft.CodeAnalysis.LanguageServer.
        """
        root_uri = PathUtils.path_to_uri(self.repository_root_path)
        root_name = os.path.basename(self.repository_root_path)
        return cast(
            InitializeParams,
            {
                "workspaceFolders": [{"uri": root_uri, "name": root_name}],
                "processId": os.getpid(),
                "rootPath": self.repository_root_path,
                "rootUri": root_uri,
                "capabilities": {
                    "window": {
                        "workDoneProgress": True,
                        "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}},
                        "showDocument": {"support": True},
                    },
                    "workspace": {
                        "applyEdit": True,
                        "workspaceEdit": {"documentChanges": True},
                        "didChangeConfiguration": {"dynamicRegistration": True},
                        "didChangeWatchedFiles": {"dynamicRegistration": True},
                        "symbol": {
                            "dynamicRegistration": True,
                            "symbolKind": {"valueSet": list(range(1, 27))},
                        },
                        "executeCommand": {"dynamicRegistration": True},
                        "configuration": True,
                        "workspaceFolders": True,
                        "workDoneProgress": True,
                    },
                    "textDocument": {
                        "synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True},
                        "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                        "signatureHelp": {
                            "dynamicRegistration": True,
                            "signatureInformation": {
                                "documentationFormat": ["markdown", "plaintext"],
                                "parameterInformation": {"labelOffsetSupport": True},
                            },
                        },
                        "definition": {"dynamicRegistration": True},
                        "references": {"dynamicRegistration": True},
                        "documentSymbol": {
                            "dynamicRegistration": True,
                            "symbolKind": {"valueSet": list(range(1, 27))},
                            "hierarchicalDocumentSymbolSupport": True,
                        },
                    },
                },
            },
        )

    def _start_server(self):
        def do_nothing(params):
            return

        def window_log_message(msg):
            """Log messages from the language server."""
            message_text = msg.get("message", "")
            level = msg.get("type", 4)  # Default to Log level

            # Map LSP message types to Python logging levels
            level_map = {1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG}  # Error  # Warning  # Info  # Log

            self.logger.log(f"LSP: {message_text}", level_map.get(level, logging.DEBUG))

        def handle_progress(params):
            """Handle progress notifications from the language server."""
            token = params.get("token", "")
            value = params.get("value", {})

            # Log raw progress for debugging
            self.logger.log(f"Progress notification received: {params}", logging.DEBUG)

            # Handle different progress notification types
            kind = value.get("kind")

            if kind == "begin":
                title = value.get("title", "Operation in progress")
                message = value.get("message", "")
                percentage = value.get("percentage")

                if percentage is not None:
                    self.logger.log(f"Progress [{token}]: {title} - {message} ({percentage}%)", logging.INFO)
                else:
                    self.logger.log(f"Progress [{token}]: {title} - {message}", logging.INFO)

            elif kind == "report":
                message = value.get("message", "")
                percentage = value.get("percentage")

                if percentage is not None:
                    self.logger.log(f"Progress [{token}]: {message} ({percentage}%)", logging.INFO)
                elif message:
                    self.logger.log(f"Progress [{token}]: {message}", logging.INFO)

            elif kind == "end":
                message = value.get("message", "Operation completed")
                self.logger.log(f"Progress [{token}]: {message}", logging.INFO)

        def handle_workspace_configuration(params):
            """Handle workspace/configuration requests from the server."""
            items = params.get("items", [])
            result = []

            for item in items:
                section = item.get("section", "")

                # Provide default values based on the configuration section
                if section.startswith(("dotnet", "csharp")):
                    # Default configuration for C# settings
                    if "enable" in section or "show" in section or "suppress" in section or "navigate" in section:
                        # Boolean settings
                        result.append(False)
                    elif "scope" in section:
                        # Scope settings - use appropriate enum values
                        if "analyzer_diagnostics_scope" in section:
                            result.append("openFiles")  # BackgroundAnalysisScope
                        elif "compiler_diagnostics_scope" in section:
                            result.append("openFiles")  # CompilerDiagnosticsScope
                        else:
                            result.append("openFiles")
                    elif section == "dotnet_member_insertion_location":
                        # ImplementTypeInsertionBehavior enum
                        result.append("with_other_members_of_the_same_kind")
                    elif section == "dotnet_property_generation_behavior":
                        # ImplementTypePropertyGenerationBehavior enum
                        result.append("prefer_throwing_properties")
                    elif "location" in section or "behavior" in section:
                        # Other enum settings - return null to avoid parsing errors
                        result.append(None)
                    else:
                        # Default for other dotnet/csharp settings
                        result.append(None)
                elif section == "tab_width" or section == "indent_size":
                    # Tab and indent settings
                    result.append(4)
                elif section == "insert_final_newline":
                    # Editor settings
                    result.append(True)
                else:
                    # Unknown configuration - return null
                    result.append(None)

            return result

        def handle_work_done_progress_create(params):
            """Handle work done progress create requests."""
            # Just acknowledge the request
            return

        def handle_register_capability(params):
            """Handle client/registerCapability requests."""
            # Just acknowledge the request - we don't need to track these for now
            return

        def handle_project_needs_restore(params):
            return

        # Set up notification handlers
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", handle_progress)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
        self.server.on_request("workspace/configuration", handle_workspace_configuration)
        self.server.on_request("window/workDoneProgress/create", handle_work_done_progress_create)
        self.server.on_request("client/registerCapability", handle_register_capability)
        self.server.on_request("workspace/_roslyn_projectNeedsRestore", handle_project_needs_restore)

        self.logger.log("Starting Microsoft.CodeAnalysis.LanguageServer process", logging.INFO)

        try:
            self.server.start()
        except Exception as e:
            self.logger.log(f"Failed to start language server process: {e}", logging.ERROR)
            raise SolidLSPException(f"Failed to start C# language server: {e}")

        # Send initialization
        initialize_params = self._get_initialize_params()

        self.logger.log("Sending initialize request to language server", logging.INFO)
        try:
            init_response = self.server.send.initialize(initialize_params)
            self.logger.log(f"Received initialize response: {init_response}", logging.DEBUG)
        except Exception as e:
            raise SolidLSPException(f"Failed to initialize C# language server for {self.repository_root_path}: {e}") from e

        # Apply diagnostic capabilities
        self._force_pull_diagnostics(init_response)

        # Verify required capabilities
        capabilities = init_response.get("capabilities", {})
        required_capabilities = [
            "textDocumentSync",
            "definitionProvider",
            "referencesProvider",
            "documentSymbolProvider",
        ]
        missing = [cap for cap in required_capabilities if cap not in capabilities]
        if missing:
            raise RuntimeError(
                f"Language server is missing required capabilities: {', '.join(missing)}. "
                "Initialization failed. Please ensure the correct version of Microsoft.CodeAnalysis.LanguageServer is installed and the .NET runtime is working."
            )

        # Complete initialization
        self.server.notify.initialized({})

        # Open solution and project files
        self._open_solution_and_projects()

        self.initialization_complete.set()
        self.completions_available.set()

        self.logger.log(
            "Microsoft.CodeAnalysis.LanguageServer initialized and ready\n"
            "Waiting for language server to index project files...\n"
            "This may take a while for large projects",
            logging.INFO,
        )

    def _force_pull_diagnostics(self, init_response: dict) -> None:
        """
        Apply the diagnostic capabilities hack.
        Forces the server to support pull diagnostics.
        """
        capabilities = init_response.get("capabilities", {})
        diagnostic_provider = capabilities.get("diagnosticProvider", {})

        # Add the diagnostic capabilities hack
        if isinstance(diagnostic_provider, dict):
            diagnostic_provider.update(
                {
                    "interFileDependencies": True,
                    "workDoneProgress": True,
                    "workspaceDiagnostics": True,
                }
            )
            self.logger.log("Applied diagnostic capabilities hack for better C# diagnostics", logging.DEBUG)

    def _open_solution_and_projects(self) -> None:
        """
        Open solution and project files using notifications.
        """
        # Find solution file
        solution_file = None
        for filename in breadth_first_file_scan(self.repository_root_path):
            if filename.endswith(".sln"):
                solution_file = filename
                break

        # Send solution/open notification if solution file found
        if solution_file:
            solution_uri = PathUtils.path_to_uri(solution_file)
            self.server.notify.send_notification("solution/open", {"solution": solution_uri})
            self.logger.log(f"Opened solution file: {solution_file}", logging.INFO)

        # Find and open project files
        project_files = []
        for filename in breadth_first_file_scan(self.repository_root_path):
            if filename.endswith(".csproj"):
                project_files.append(filename)

        # Send project/open notifications for each project file
        if project_files:
            project_uris = [PathUtils.path_to_uri(project_file) for project_file in project_files]
            self.server.notify.send_notification("project/open", {"projects": project_uris})
            self.logger.log(f"Opened project files: {project_files}", logging.DEBUG)

    @override
    def _get_wait_time_for_cross_file_referencing(self) -> float:
        return 2

```

--------------------------------------------------------------------------------
/test/solidlsp/ruby/test_ruby_symbol_retrieval.py:
--------------------------------------------------------------------------------

```python
"""
Tests for the Ruby language server symbol-related functionality.

These tests focus on the following methods:
- request_containing_symbol
- request_referencing_symbols
- request_defining_symbol
- request_document_symbols integration
"""

import os

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind

pytestmark = pytest.mark.ruby


class TestRubyLanguageServerSymbols:
    """Test the Ruby language server's symbol-related functionality."""

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_containing_symbol_method(self, language_server: SolidLanguageServer) -> None:
        """Test request_containing_symbol for a method."""
        # Test for a position inside the create_user method
        file_path = os.path.join("services.rb")
        # Look for a position inside the create_user method body
        containing_symbol = language_server.request_containing_symbol(file_path, 11, 10, include_body=True)

        # Verify that we found the containing symbol
        assert containing_symbol is not None, "Should find containing symbol for method position"
        assert containing_symbol["name"] == "create_user", f"Expected 'create_user', got '{containing_symbol['name']}'"
        assert (
            containing_symbol["kind"] == SymbolKind.Method.value
        ), f"Expected Method kind ({SymbolKind.Method.value}), got {containing_symbol['kind']}"

        # Verify location information
        assert "location" in containing_symbol, "Containing symbol should have location information"
        location = containing_symbol["location"]
        assert "range" in location, "Location should contain range information"
        assert "start" in location["range"], "Range should have start position"
        assert "end" in location["range"], "Range should have end position"

        # Verify container information
        if "containerName" in containing_symbol:
            assert containing_symbol["containerName"] in [
                "Services::UserService",
                "UserService",
            ], f"Expected UserService container, got '{containing_symbol['containerName']}'"

        # Verify body content if available
        if "body" in containing_symbol:
            body = containing_symbol["body"]
            assert "def create_user" in body, "Method body should contain method definition"
            assert len(body.strip()) > 0, "Method body should not be empty"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None:
        """Test request_containing_symbol for a class."""
        # Test for a position inside the UserService class but outside any method
        file_path = os.path.join("services.rb")
        # Line around the class definition
        containing_symbol = language_server.request_containing_symbol(file_path, 5, 5)

        # Verify that we found the containing symbol
        assert containing_symbol is not None, "Should find containing symbol for class position"
        assert containing_symbol["name"] == "UserService", f"Expected 'UserService', got '{containing_symbol['name']}'"
        assert (
            containing_symbol["kind"] == SymbolKind.Class.value
        ), f"Expected Class kind ({SymbolKind.Class.value}), got {containing_symbol['kind']}"

        # Verify location information exists
        assert "location" in containing_symbol, "Class symbol should have location information"
        location = containing_symbol["location"]
        assert "range" in location, "Location should contain range"
        assert "start" in location["range"] and "end" in location["range"], "Range should have start and end positions"

        # Verify the class is properly nested in the Services module
        if "containerName" in containing_symbol:
            assert (
                containing_symbol["containerName"] == "Services"
            ), f"Expected 'Services' as container, got '{containing_symbol['containerName']}'"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_containing_symbol_module(self, language_server: SolidLanguageServer) -> None:
        """Test request_containing_symbol for a module context."""
        # Test that we can find the Services module in document symbols
        file_path = os.path.join("services.rb")
        symbols, _roots = language_server.request_document_symbols(file_path)

        # Verify Services module appears in document symbols
        services_module = None
        for symbol in symbols:
            if symbol.get("name") == "Services" and symbol.get("kind") == SymbolKind.Module:
                services_module = symbol
                break

        assert services_module is not None, "Services module not found in document symbols"

        # Test that UserService class has Services as container
        # Position inside UserService class
        containing_symbol = language_server.request_containing_symbol(file_path, 4, 8)
        assert containing_symbol is not None
        assert containing_symbol["name"] == "UserService"
        assert containing_symbol["kind"] == SymbolKind.Class
        # Verify the module context is preserved in containerName (if supported by the language server)
        # ruby-lsp doesn't provide containerName, but Solargraph does
        if "containerName" in containing_symbol:
            assert containing_symbol.get("containerName") == "Services"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_containing_symbol_nested_class(self, language_server: SolidLanguageServer) -> None:
        """Test request_containing_symbol with nested classes."""
        # Test for a position inside a nested class method
        file_path = os.path.join("nested.rb")
        # Position inside NestedClass.find_me method
        containing_symbol = language_server.request_containing_symbol(file_path, 20, 10)

        # Verify that we found the innermost containing symbol
        assert containing_symbol is not None
        assert containing_symbol["name"] == "find_me"
        assert containing_symbol["kind"] == SymbolKind.Method

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None:
        """Test request_containing_symbol for a position with no containing symbol."""
        # Test for a position outside any class/method (e.g., in requires)
        file_path = os.path.join("services.rb")
        # Line 1 is a require statement, not inside any class or method
        containing_symbol = language_server.request_containing_symbol(file_path, 1, 5)

        # Should return None or an empty dictionary
        assert containing_symbol is None or containing_symbol == {}

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_referencing_symbols_method(self, language_server: SolidLanguageServer) -> None:
        """Test request_referencing_symbols for a method."""
        # Test referencing symbols for create_user method
        file_path = os.path.join("services.rb")
        # Line containing the create_user method definition
        symbols, _roots = language_server.request_document_symbols(file_path)
        create_user_symbol = None

        # Find create_user method in the document symbols (Ruby returns flat list)
        for symbol in symbols:
            if symbol.get("name") == "create_user":
                create_user_symbol = symbol
                break

        if not create_user_symbol or "selectionRange" not in create_user_symbol:
            pytest.skip("create_user symbol or its selectionRange not found")

        sel_start = create_user_symbol["selectionRange"]["start"]
        ref_symbols = [
            ref.symbol for ref in language_server.request_referencing_symbols(file_path, sel_start["line"], sel_start["character"])
        ]

        # We might not have references in our simple test setup, so just verify structure
        for symbol in ref_symbols:
            assert "name" in symbol
            assert "kind" in symbol

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_referencing_symbols_class(self, language_server: SolidLanguageServer) -> None:
        """Test request_referencing_symbols for a class."""
        # Test referencing symbols for User class
        file_path = os.path.join("models.rb")
        # Find User class in document symbols
        symbols, _roots = language_server.request_document_symbols(file_path)
        user_symbol = None

        for symbol in symbols:
            if symbol.get("name") == "User":
                user_symbol = symbol
                break

        if not user_symbol or "selectionRange" not in user_symbol:
            pytest.skip("User symbol or its selectionRange not found")

        sel_start = user_symbol["selectionRange"]["start"]
        ref_symbols = [
            ref.symbol for ref in language_server.request_referencing_symbols(file_path, sel_start["line"], sel_start["character"])
        ]

        # Verify structure of referencing symbols
        for symbol in ref_symbols:
            assert "name" in symbol
            assert "kind" in symbol
            if "location" in symbol and "range" in symbol["location"]:
                assert "start" in symbol["location"]["range"]
                assert "end" in symbol["location"]["range"]

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None:
        """Test request_defining_symbol for a variable usage."""
        # Test finding the definition of a variable in a method
        file_path = os.path.join("services.rb")
        # Look for @users variable usage
        defining_symbol = language_server.request_defining_symbol(file_path, 12, 10)

        # This test might fail if the language server doesn't support it well
        if defining_symbol is not None:
            assert "name" in defining_symbol
            assert "kind" in defining_symbol

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_defining_symbol_class(self, language_server: SolidLanguageServer) -> None:
        """Test request_defining_symbol for a class reference."""
        # Test finding the definition of the User class used in services
        file_path = os.path.join("services.rb")
        # Line that references User class
        defining_symbol = language_server.request_defining_symbol(file_path, 11, 15)

        # This might not work perfectly in all Ruby language servers
        if defining_symbol is not None:
            assert "name" in defining_symbol
            # The name might be "User" or the method that contains it
            assert defining_symbol.get("name") is not None

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None:
        """Test request_defining_symbol for a position with no symbol."""
        # Test for a position with no symbol (e.g., whitespace or comment)
        file_path = os.path.join("services.rb")
        # Line 3 is likely a blank line or comment
        defining_symbol = language_server.request_defining_symbol(file_path, 3, 0)

        # Should return None for positions with no symbol
        assert defining_symbol is None or defining_symbol == {}

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_defining_symbol_nested_class(self, language_server: SolidLanguageServer) -> None:
        """Test request_defining_symbol for nested class access."""
        # Test finding definition of NestedClass
        file_path = os.path.join("nested.rb")
        # Position where NestedClass is referenced
        defining_symbol = language_server.request_defining_symbol(file_path, 44, 25)

        # This is challenging for many language servers
        if defining_symbol is not None:
            assert "name" in defining_symbol
            assert defining_symbol.get("name") in ["NestedClass", "OuterClass"]

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None:
        """Test the integration between different symbol-related methods."""
        file_path = os.path.join("models.rb")

        # Step 1: Find a method we know exists
        containing_symbol = language_server.request_containing_symbol(file_path, 8, 5)  # inside initialize method
        if containing_symbol is not None:
            assert containing_symbol["name"] == "initialize"

            # Step 2: Get the defining symbol for the same position
            defining_symbol = language_server.request_defining_symbol(file_path, 8, 5)
            if defining_symbol is not None:
                assert defining_symbol["name"] == "initialize"

                # Step 3: Verify that they refer to the same symbol type
                assert defining_symbol["kind"] == containing_symbol["kind"]

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_symbol_tree_structure_basic(self, language_server: SolidLanguageServer) -> None:
        """Test that the symbol tree structure includes Ruby symbols."""
        # Get all symbols in the test repository
        repo_structure = language_server.request_full_symbol_tree()
        assert len(repo_structure) >= 1

        # Look for our Ruby files in the structure
        found_ruby_files = False
        for root in repo_structure:
            if "children" in root:
                for child in root["children"]:
                    if child.get("name") in ["models", "services", "nested"]:
                        found_ruby_files = True
                        break

        # We should find at least some Ruby files in the symbol tree
        assert found_ruby_files, "Ruby files not found in symbol tree"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_document_symbols_detailed(self, language_server: SolidLanguageServer) -> None:
        """Test document symbols for detailed Ruby file structure."""
        file_path = os.path.join("models.rb")
        symbols, roots = language_server.request_document_symbols(file_path)

        # Verify we have symbols
        assert len(symbols) > 0 or len(roots) > 0

        # Look for expected class names
        symbol_names = set()
        all_symbols = symbols if symbols else roots

        for symbol in all_symbols:
            symbol_names.add(symbol.get("name"))
            # Add children names too
            if "children" in symbol:
                for child in symbol["children"]:
                    symbol_names.add(child.get("name"))

        # We should find at least some of our defined classes/methods
        expected_symbols = {"User", "Item", "Order", "ItemHelpers"}
        found_symbols = symbol_names.intersection(expected_symbols)
        assert len(found_symbols) > 0, f"Expected symbols not found. Found: {symbol_names}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_module_and_class_hierarchy(self, language_server: SolidLanguageServer) -> None:
        """Test symbol detection for modules and nested class hierarchies."""
        file_path = os.path.join("nested.rb")
        symbols, roots = language_server.request_document_symbols(file_path)

        # Verify we can detect the nested structure
        assert len(symbols) > 0 or len(roots) > 0

        # Look for OuterClass and its nested elements
        symbol_names = set()
        all_symbols = symbols if symbols else roots

        for symbol in all_symbols:
            symbol_names.add(symbol.get("name"))
            if "children" in symbol:
                for child in symbol["children"]:
                    symbol_names.add(child.get("name"))
                    # Check deeply nested too
                    if "children" in child:
                        for grandchild in child["children"]:
                            symbol_names.add(grandchild.get("name"))

        # Should find the outer class at minimum
        assert "OuterClass" in symbol_names, f"OuterClass not found in symbols: {symbol_names}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_references_to_variables(self, language_server: SolidLanguageServer) -> None:
        """Test request_referencing_symbols for a variable with detailed verification."""
        file_path = os.path.join("variables.rb")
        # Test references to @status variable in DataContainer class (around line 9)
        ref_symbols = [ref.symbol for ref in language_server.request_referencing_symbols(file_path, 8, 4)]

        if len(ref_symbols) > 0:
            # Verify we have references
            assert len(ref_symbols) > 0, "Should find references to @status variable"

            # Check that we have location information
            ref_with_locations = [ref for ref in ref_symbols if "location" in ref and "range" in ref["location"]]
            assert len(ref_with_locations) > 0, "References should include location information"

            # Verify line numbers are reasonable (should be within the file)
            ref_lines = [ref["location"]["range"]["start"]["line"] for ref in ref_with_locations]
            assert all(line >= 0 for line in ref_lines), "Reference lines should be valid"

            # Check for specific reference locations we expect
            # Lines where @status is modified/accessed
            expected_line_ranges = [(20, 40), (45, 70)]  # Approximate ranges
            found_in_expected_range = any(any(start <= line <= end for start, end in expected_line_ranges) for line in ref_lines)
            assert found_in_expected_range, f"Expected references in ranges {expected_line_ranges}, found lines: {ref_lines}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_referencing_symbols_parameter(self, language_server: SolidLanguageServer) -> None:
        """Test request_referencing_symbols for a method parameter."""
        # Test referencing symbols for a method parameter in get_user method
        file_path = os.path.join("services.rb")
        # Find get_user method and test parameter references
        symbols, _roots = language_server.request_document_symbols(file_path)
        get_user_symbol = None

        for symbol in symbols:
            if symbol.get("name") == "get_user":
                get_user_symbol = symbol
                break

        if not get_user_symbol or "selectionRange" not in get_user_symbol:
            pytest.skip("get_user symbol or its selectionRange not found")

        # Test parameter reference within method body
        method_start_line = get_user_symbol["selectionRange"]["start"]["line"]
        ref_symbols = [
            ref.symbol
            for ref in language_server.request_referencing_symbols(file_path, method_start_line + 1, 10)  # Position within method body
        ]

        # Verify structure of referencing symbols
        for symbol in ref_symbols:
            assert "name" in symbol, "Symbol should have name"
            assert "kind" in symbol, "Symbol should have kind"
            if "location" in symbol and "range" in symbol["location"]:
                range_info = symbol["location"]["range"]
                assert "start" in range_info, "Range should have start"
                assert "end" in range_info, "Range should have end"
                # Verify line number is valid (references can be before method definition too)
                assert range_info["start"]["line"] >= 0, "Reference line should be valid"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None:
        """Test request_referencing_symbols for a position with no symbol."""
        # Test for a position with no symbol (comment or blank line)
        file_path = os.path.join("services.rb")

        # Try multiple positions that should have no symbols
        test_positions = [(1, 0), (2, 0)]  # Comment/require lines

        for line, char in test_positions:
            try:
                ref_symbols = [ref.symbol for ref in language_server.request_referencing_symbols(file_path, line, char)]
                # If we get here, make sure we got an empty result or minimal results
                if ref_symbols:
                    # Some language servers might return minimal info, verify it's reasonable
                    assert len(ref_symbols) <= 3, f"Expected few/no references at line {line}, got {len(ref_symbols)}"

            except Exception as e:
                # Some language servers throw exceptions for invalid positions, which is acceptable
                assert (
                    "symbol" in str(e).lower() or "position" in str(e).lower() or "reference" in str(e).lower()
                ), f"Exception should be related to symbol/position/reference issues, got: {e}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
        """Test that request_dir_overview returns correct symbol information for files in a directory."""
        # Get overview of the test repo directory
        overview = language_server.request_dir_overview(".")

        # Verify that we have entries for our main files
        expected_files = ["services.rb", "models.rb", "variables.rb", "nested.rb"]
        found_files = []

        for file_path in overview.keys():
            for expected in expected_files:
                if expected in file_path:
                    found_files.append(expected)
                    break

        assert len(found_files) >= 2, f"Should find at least 2 expected files, found: {found_files}"

        # Test specific symbols from services.rb if it exists
        services_file_key = None
        for file_path in overview.keys():
            if "services.rb" in file_path:
                services_file_key = file_path
                break

        if services_file_key:
            services_symbols = overview[services_file_key]
            assert len(services_symbols) > 0, "services.rb should have symbols"

            # Check for expected symbols with detailed verification
            symbol_names = [s[0] for s in services_symbols if isinstance(s, tuple) and len(s) > 0]
            if not symbol_names:  # If not tuples, try different format
                symbol_names = [s.get("name") for s in services_symbols if hasattr(s, "get")]

            expected_symbols = ["Services", "UserService", "ItemService"]
            found_expected = [name for name in expected_symbols if name in symbol_names]
            assert len(found_expected) >= 1, f"Should find at least one expected symbol, found: {found_expected} in {symbol_names}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_document_overview(self, language_server: SolidLanguageServer) -> None:
        """Test that request_document_overview returns correct symbol information for a file."""
        # Get overview of the user_management.rb file
        file_path = os.path.join("examples", "user_management.rb")
        overview = language_server.request_document_overview(file_path)

        # Verify that we have symbol information
        assert len(overview) > 0, "Document overview should contain symbols"

        # Look for expected symbols from the file
        symbol_names = set()
        for s_info in overview:
            if isinstance(s_info, tuple) and len(s_info) > 0:
                symbol_names.add(s_info[0])
            elif hasattr(s_info, "get"):
                symbol_names.add(s_info.get("name"))
            elif isinstance(s_info, str):
                symbol_names.add(s_info)

        # We should find some of our defined classes/methods
        expected_symbols = {"UserStats", "UserManager", "process_user_data", "main"}
        found_symbols = symbol_names.intersection(expected_symbols)
        assert len(found_symbols) > 0, f"Expected to find some symbols from {expected_symbols}, found: {symbol_names}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_containing_symbol_variable(self, language_server: SolidLanguageServer) -> None:
        """Test request_containing_symbol where the target is a variable."""
        # Test for a position inside a variable definition or usage
        file_path = os.path.join("variables.rb")
        # Position around a variable assignment (e.g., @status = "pending")
        containing_symbol = language_server.request_containing_symbol(file_path, 10, 5)

        # Verify that we found a containing symbol (likely the method or class)
        if containing_symbol is not None:
            assert "name" in containing_symbol, "Containing symbol should have a name"
            assert "kind" in containing_symbol, "Containing symbol should have a kind"
            # The containing symbol should be a method, class, or similar construct
            expected_kinds = [SymbolKind.Method, SymbolKind.Class, SymbolKind.Function, SymbolKind.Constructor]
            assert containing_symbol["kind"] in [
                k.value for k in expected_kinds
            ], f"Expected containing symbol to be method/class/function, got kind: {containing_symbol['kind']}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
        """Test request_containing_symbol for a function (not method)."""
        # Test for a position inside a standalone function
        file_path = os.path.join("variables.rb")
        # Position inside the demonstrate_variable_usage function
        containing_symbol = language_server.request_containing_symbol(file_path, 100, 10)

        if containing_symbol is not None:
            assert containing_symbol["name"] in [
                "demonstrate_variable_usage",
                "main",
            ], f"Expected function name, got: {containing_symbol['name']}"
            assert containing_symbol["kind"] in [
                SymbolKind.Function.value,
                SymbolKind.Method.value,
            ], f"Expected function or method kind, got: {containing_symbol['kind']}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
        """Test request_containing_symbol with nested scopes."""
        # Test for a position inside a method which is inside a class
        file_path = os.path.join("services.rb")
        # Position inside create_user method within UserService class
        containing_symbol = language_server.request_containing_symbol(file_path, 12, 15)

        # Verify that we found the innermost containing symbol (the method)
        assert containing_symbol is not None
        assert containing_symbol["name"] == "create_user"
        assert containing_symbol["kind"] == SymbolKind.Method

        # Verify the container context is preserved
        if "containerName" in containing_symbol:
            assert "UserService" in containing_symbol["containerName"]

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_symbol_tree_structure_subdir(self, language_server: SolidLanguageServer) -> None:
        """Test that the symbol tree structure correctly handles subdirectories."""
        # Get symbols within the examples subdirectory
        examples_structure = language_server.request_full_symbol_tree(within_relative_path="examples")

        if len(examples_structure) > 0:
            # Should find the examples directory structure
            assert len(examples_structure) >= 1, "Should find examples directory structure"

            # Look for the user_management file in the structure
            found_user_management = False
            for root in examples_structure:
                if "children" in root:
                    for child in root["children"]:
                        if "user_management" in child.get("name", ""):
                            found_user_management = True
                            # Verify the structure includes symbol information
                            if "children" in child:
                                child_names = [c.get("name") for c in child["children"]]
                                expected_names = ["UserStats", "UserManager", "process_user_data"]
                                found_expected = [name for name in expected_names if name in child_names]
                                assert (
                                    len(found_expected) > 0
                                ), f"Should find symbols in user_management, expected {expected_names}, found {child_names}"
                            break

            if not found_user_management:
                pytest.skip("user_management file not found in examples subdirectory structure")

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None:
        """Test request_defining_symbol for an imported/required class."""
        # Test finding the definition of a class used from another file
        file_path = os.path.join("examples", "user_management.rb")
        # Position where Services::UserService is referenced
        defining_symbol = language_server.request_defining_symbol(file_path, 25, 20)

        # This might not work perfectly in all Ruby language servers due to require complexity
        if defining_symbol is not None:
            assert "name" in defining_symbol
            # The defining symbol should relate to UserService or Services
            # The defining symbol should relate to UserService, Services, or the containing class
            # Different language servers may resolve this differently
            expected_names = ["UserService", "Services", "new", "UserManager"]
            assert defining_symbol.get("name") in expected_names, f"Expected one of {expected_names}, got: {defining_symbol.get('name')}"

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_defining_symbol_method_call(self, language_server: SolidLanguageServer) -> None:
        """Test request_defining_symbol for a method call."""
        # Test finding the definition of a method being called
        file_path = os.path.join("examples", "user_management.rb")
        # Position at a method call like create_user
        defining_symbol = language_server.request_defining_symbol(file_path, 30, 15)

        # Verify that we can find method definitions
        if defining_symbol is not None:
            assert "name" in defining_symbol
            assert "kind" in defining_symbol
            # Should be a method or constructor
            assert defining_symbol.get("kind") in [SymbolKind.Method.value, SymbolKind.Constructor.value, SymbolKind.Function.value]

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_request_defining_symbol_nested_function(self, language_server: SolidLanguageServer) -> None:
        """Test request_defining_symbol for a nested function or block."""
        # Test finding definition within nested contexts
        file_path = os.path.join("nested.rb")
        # Position inside or referencing nested functionality
        defining_symbol = language_server.request_defining_symbol(file_path, 15, 10)

        # This is challenging for many language servers
        if defining_symbol is not None:
            assert "name" in defining_symbol
            assert "kind" in defining_symbol
            # Could be method, function, or variable depending on implementation
            valid_kinds = [SymbolKind.Method.value, SymbolKind.Function.value, SymbolKind.Variable.value, SymbolKind.Class.value]
            assert defining_symbol.get("kind") in valid_kinds

    @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
    def test_containing_symbol_of_var_is_file(self, language_server: SolidLanguageServer) -> None:
        """Test that the containing symbol of a file-level variable is handled appropriately."""
        # Test behavior with file-level variables or constants
        file_path = os.path.join("variables.rb")
        # Position at file-level variable/constant
        containing_symbol = language_server.request_containing_symbol(file_path, 5, 5)

        # Different language servers handle file-level symbols differently
        # Some return None, others return file-level containers
        if containing_symbol is not None:
            # If we get a symbol, verify its structure
            assert "name" in containing_symbol
            assert "kind" in containing_symbol

```

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

```python
"""
Provides Java specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Java.
"""

import dataclasses
import logging
import os
import pathlib
import shutil
import threading
import uuid
from pathlib import PurePath

from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import FileUtils, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings


@dataclasses.dataclass
class RuntimeDependencyPaths:
    """
    Stores the paths to the runtime dependencies of EclipseJDTLS
    """

    gradle_path: str
    lombok_jar_path: str
    jre_path: str
    jre_home_path: str
    jdtls_launcher_jar_path: str
    jdtls_readonly_config_path: str
    intellicode_jar_path: str
    intellisense_members_path: str


class EclipseJDTLS(SolidLanguageServer):
    """
    The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class
    """

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        """
        Creates a new EclipseJDTLS instance initializing the language server settings appropriately.
        This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
        """
        runtime_dependency_paths = self._setupRuntimeDependencies(logger, config, solidlsp_settings)
        self.runtime_dependency_paths = runtime_dependency_paths

        # ws_dir is the workspace directory for the EclipseJDTLS server
        ws_dir = str(
            PurePath(
                solidlsp_settings.ls_resources_dir,
                "EclipseJDTLS",
                "workspaces",
                uuid.uuid4().hex,
            )
        )

        # shared_cache_location is the global cache used by Eclipse JDTLS across all workspaces
        shared_cache_location = str(PurePath(solidlsp_settings.ls_resources_dir, "lsp", "EclipseJDTLS", "sharedIndex"))
        os.makedirs(shared_cache_location, exist_ok=True)
        os.makedirs(ws_dir, exist_ok=True)

        jre_path = self.runtime_dependency_paths.jre_path
        lombok_jar_path = self.runtime_dependency_paths.lombok_jar_path

        jdtls_launcher_jar = self.runtime_dependency_paths.jdtls_launcher_jar_path

        data_dir = str(PurePath(ws_dir, "data_dir"))
        jdtls_config_path = str(PurePath(ws_dir, "config_path"))

        jdtls_readonly_config_path = self.runtime_dependency_paths.jdtls_readonly_config_path

        if not os.path.exists(jdtls_config_path):
            shutil.copytree(jdtls_readonly_config_path, jdtls_config_path)

        for static_path in [
            jre_path,
            lombok_jar_path,
            jdtls_launcher_jar,
            jdtls_config_path,
            jdtls_readonly_config_path,
        ]:
            assert os.path.exists(static_path), static_path

        # TODO: Add "self.runtime_dependency_paths.jre_home_path"/bin to $PATH as well
        proc_env = {"syntaxserver": "false", "JAVA_HOME": self.runtime_dependency_paths.jre_home_path}
        proc_cwd = repository_root_path
        cmd = " ".join(
            [
                jre_path,
                "--add-modules=ALL-SYSTEM",
                "--add-opens",
                "java.base/java.util=ALL-UNNAMED",
                "--add-opens",
                "java.base/java.lang=ALL-UNNAMED",
                "--add-opens",
                "java.base/sun.nio.fs=ALL-UNNAMED",
                "-Declipse.application=org.eclipse.jdt.ls.core.id1",
                "-Dosgi.bundles.defaultStartLevel=4",
                "-Declipse.product=org.eclipse.jdt.ls.core.product",
                "-Djava.import.generatesMetadataFilesAtProjectRoot=false",
                "-Dfile.encoding=utf8",
                "-noverify",
                "-XX:+UseParallelGC",
                "-XX:GCTimeRatio=4",
                "-XX:AdaptiveSizePolicyWeight=90",
                "-Dsun.zip.disableMemoryMapping=true",
                "-Djava.lsp.joinOnCompletion=true",
                "-Xmx3G",
                "-Xms100m",
                "-Xlog:disable",
                "-Dlog.level=ALL",
                f'"-javaagent:{lombok_jar_path}"',
                f'"-Djdt.core.sharedIndexLocation={shared_cache_location}"',
                "-jar",
                f'"{jdtls_launcher_jar}"',
                "-configuration",
                f'"{jdtls_config_path}"',
                "-data",
                f'"{data_dir}"',
            ]
        )

        self.service_ready_event = threading.Event()
        self.intellicode_enable_command_available = threading.Event()
        self.initialize_searcher_command_available = threading.Event()

        super().__init__(
            config, logger, repository_root_path, ProcessLaunchInfo(cmd, proc_env, proc_cwd), "java", solidlsp_settings=solidlsp_settings
        )

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        # Ignore common Java build directories from different build tools:
        # - Maven: target
        # - Gradle: build, .gradle
        # - Eclipse: bin, .settings
        # - IntelliJ IDEA: out, .idea
        # - General: classes, dist, lib
        return super().is_ignored_dirname(dirname) or dirname in [
            "target",  # Maven
            "build",  # Gradle
            "bin",  # Eclipse
            "out",  # IntelliJ IDEA
            "classes",  # General
            "dist",  # General
            "lib",  # General
        ]

    @classmethod
    def _setupRuntimeDependencies(
        cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
    ) -> RuntimeDependencyPaths:
        """
        Setup runtime dependencies for EclipseJDTLS and return the paths.
        """
        platformId = PlatformUtils.get_platform_id()

        runtime_dependencies = {
            "gradle": {
                "platform-agnostic": {
                    "url": "https://services.gradle.org/distributions/gradle-8.14.2-bin.zip",
                    "archiveType": "zip",
                    "relative_extraction_path": ".",
                }
            },
            "vscode-java": {
                "darwin-arm64": {
                    "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
                    "archiveType": "zip",
                    "relative_extraction_path": "vscode-java",
                },
                "osx-arm64": {
                    "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
                    "archiveType": "zip",
                    "relative_extraction_path": "vscode-java",
                    "jre_home_path": "extension/jre/21.0.7-macosx-aarch64",
                    "jre_path": "extension/jre/21.0.7-macosx-aarch64/bin/java",
                    "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                    "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                    "jdtls_readonly_config_path": "extension/server/config_mac_arm",
                },
                "osx-x64": {
                    "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix",
                    "archiveType": "zip",
                    "relative_extraction_path": "vscode-java",
                    "jre_home_path": "extension/jre/21.0.7-macosx-x86_64",
                    "jre_path": "extension/jre/21.0.7-macosx-x86_64/bin/java",
                    "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                    "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                    "jdtls_readonly_config_path": "extension/server/config_mac",
                },
                "linux-arm64": {
                    "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix",
                    "archiveType": "zip",
                    "relative_extraction_path": "vscode-java",
                },
                "linux-x64": {
                    "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix",
                    "archiveType": "zip",
                    "relative_extraction_path": "vscode-java",
                    "jre_home_path": "extension/jre/21.0.7-linux-x86_64",
                    "jre_path": "extension/jre/21.0.7-linux-x86_64/bin/java",
                    "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                    "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                    "jdtls_readonly_config_path": "extension/server/config_linux",
                },
                "win-x64": {
                    "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix",
                    "archiveType": "zip",
                    "relative_extraction_path": "vscode-java",
                    "jre_home_path": "extension/jre/21.0.7-win32-x86_64",
                    "jre_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe",
                    "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                    "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                    "jdtls_readonly_config_path": "extension/server/config_win",
                },
            },
            "intellicode": {
                "platform-agnostic": {
                    "url": "https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/1.2.30/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage",
                    "alternate_url": "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/VisualStudioExptTeam/vsextensions/vscodeintellicode/1.2.30/vspackage",
                    "archiveType": "zip",
                    "relative_extraction_path": "intellicode",
                    "intellicode_jar_path": "extension/dist/com.microsoft.jdtls.intellicode.core-0.7.0.jar",
                    "intellisense_members_path": "extension/dist/bundledModels/java_intellisense-members",
                }
            },
        }

        # assert platformId.value in [
        #     "linux-x64",
        #     "win-x64",
        # ], "Only linux-x64 platform is supported for in multilspy at the moment"

        gradle_path = str(
            PurePath(
                cls.ls_resources_dir(solidlsp_settings),
                "gradle-8.14.2",
            )
        )

        if not os.path.exists(gradle_path):
            FileUtils.download_and_extract_archive(
                logger,
                runtime_dependencies["gradle"]["platform-agnostic"]["url"],
                str(PurePath(gradle_path).parent),
                runtime_dependencies["gradle"]["platform-agnostic"]["archiveType"],
            )

        assert os.path.exists(gradle_path)

        dependency = runtime_dependencies["vscode-java"][platformId.value]
        vscode_java_path = str(PurePath(cls.ls_resources_dir(solidlsp_settings), dependency["relative_extraction_path"]))
        os.makedirs(vscode_java_path, exist_ok=True)
        jre_home_path = str(PurePath(vscode_java_path, dependency["jre_home_path"]))
        jre_path = str(PurePath(vscode_java_path, dependency["jre_path"]))
        lombok_jar_path = str(PurePath(vscode_java_path, dependency["lombok_jar_path"]))
        jdtls_launcher_jar_path = str(PurePath(vscode_java_path, dependency["jdtls_launcher_jar_path"]))
        jdtls_readonly_config_path = str(PurePath(vscode_java_path, dependency["jdtls_readonly_config_path"]))
        if not all(
            [
                os.path.exists(vscode_java_path),
                os.path.exists(jre_home_path),
                os.path.exists(jre_path),
                os.path.exists(lombok_jar_path),
                os.path.exists(jdtls_launcher_jar_path),
                os.path.exists(jdtls_readonly_config_path),
            ]
        ):
            FileUtils.download_and_extract_archive(logger, dependency["url"], vscode_java_path, dependency["archiveType"])

        os.chmod(jre_path, 0o755)

        assert os.path.exists(vscode_java_path)
        assert os.path.exists(jre_home_path)
        assert os.path.exists(jre_path)
        assert os.path.exists(lombok_jar_path)
        assert os.path.exists(jdtls_launcher_jar_path)
        assert os.path.exists(jdtls_readonly_config_path)

        dependency = runtime_dependencies["intellicode"]["platform-agnostic"]
        intellicode_directory_path = str(PurePath(cls.ls_resources_dir(solidlsp_settings), dependency["relative_extraction_path"]))
        os.makedirs(intellicode_directory_path, exist_ok=True)
        intellicode_jar_path = str(PurePath(intellicode_directory_path, dependency["intellicode_jar_path"]))
        intellisense_members_path = str(PurePath(intellicode_directory_path, dependency["intellisense_members_path"]))
        if not all(
            [
                os.path.exists(intellicode_directory_path),
                os.path.exists(intellicode_jar_path),
                os.path.exists(intellisense_members_path),
            ]
        ):
            FileUtils.download_and_extract_archive(logger, dependency["url"], intellicode_directory_path, dependency["archiveType"])

        assert os.path.exists(intellicode_directory_path)
        assert os.path.exists(intellicode_jar_path)
        assert os.path.exists(intellisense_members_path)

        return RuntimeDependencyPaths(
            gradle_path=gradle_path,
            lombok_jar_path=lombok_jar_path,
            jre_path=jre_path,
            jre_home_path=jre_home_path,
            jdtls_launcher_jar_path=jdtls_launcher_jar_path,
            jdtls_readonly_config_path=jdtls_readonly_config_path,
            intellicode_jar_path=intellicode_jar_path,
            intellisense_members_path=intellisense_members_path,
        )

    def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize parameters for the EclipseJDTLS server.
        """
        # Look into https://github.com/eclipse/eclipse.jdt.ls/blob/master/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java to understand all the options available

        if not os.path.isabs(repository_absolute_path):
            repository_absolute_path = os.path.abspath(repository_absolute_path)
        repo_uri = pathlib.Path(repository_absolute_path).as_uri()

        initialize_params = {
            "locale": "en",
            "rootPath": repository_absolute_path,
            "rootUri": pathlib.Path(repository_absolute_path).as_uri(),
            "capabilities": {
                "workspace": {
                    "applyEdit": True,
                    "workspaceEdit": {
                        "documentChanges": True,
                        "resourceOperations": ["create", "rename", "delete"],
                        "failureHandling": "textOnlyTransactional",
                        "normalizesLineEndings": True,
                        "changeAnnotationSupport": {"groupsOnLabel": True},
                    },
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True},
                    "symbol": {
                        "dynamicRegistration": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                        "tagSupport": {"valueSet": [1]},
                        "resolveSupport": {"properties": ["location.range"]},
                    },
                    "codeLens": {"refreshSupport": True},
                    "executeCommand": {"dynamicRegistration": True},
                    "configuration": True,
                    "workspaceFolders": True,
                    "semanticTokens": {"refreshSupport": True},
                    "fileOperations": {
                        "dynamicRegistration": True,
                        "didCreate": True,
                        "didRename": True,
                        "didDelete": True,
                        "willCreate": True,
                        "willRename": True,
                        "willDelete": True,
                    },
                    "inlineValue": {"refreshSupport": True},
                    "inlayHint": {"refreshSupport": True},
                    "diagnostics": {"refreshSupport": True},
                },
                "textDocument": {
                    "publishDiagnostics": {
                        "relatedInformation": True,
                        "versionSupport": False,
                        "tagSupport": {"valueSet": [1, 2]},
                        "codeDescriptionSupport": True,
                        "dataSupport": True,
                    },
                    "synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True},
                    # TODO: we have an assert that completion provider is not included in the capabilities at server startup
                    #   Removing this will cause the assert to fail. Investigate why this is the case, simplify config
                    "completion": {
                        "dynamicRegistration": True,
                        "contextSupport": True,
                        "completionItem": {
                            "snippetSupport": False,
                            "commitCharactersSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "deprecatedSupport": True,
                            "preselectSupport": True,
                            "tagSupport": {"valueSet": [1]},
                            "insertReplaceSupport": False,
                            "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]},
                            "insertTextModeSupport": {"valueSet": [1, 2]},
                            "labelDetailsSupport": True,
                        },
                        "insertTextMode": 2,
                        "completionItemKind": {
                            "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
                        },
                        "completionList": {"itemDefaults": ["commitCharacters", "editRange", "insertTextFormat", "insertTextMode"]},
                    },
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                    "signatureHelp": {
                        "dynamicRegistration": True,
                        "signatureInformation": {
                            "documentationFormat": ["markdown", "plaintext"],
                            "parameterInformation": {"labelOffsetSupport": True},
                            "activeParameterSupport": True,
                        },
                    },
                    "definition": {"dynamicRegistration": True, "linkSupport": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                        "hierarchicalDocumentSymbolSupport": True,
                        "tagSupport": {"valueSet": [1]},
                        "labelSupport": True,
                    },
                    "rename": {
                        "dynamicRegistration": True,
                        "prepareSupport": True,
                        "prepareSupportDefaultBehavior": 1,
                        "honorsChangeAnnotations": True,
                    },
                    "documentLink": {"dynamicRegistration": True, "tooltipSupport": True},
                    "typeDefinition": {"dynamicRegistration": True, "linkSupport": True},
                    "implementation": {"dynamicRegistration": True, "linkSupport": True},
                    "colorProvider": {"dynamicRegistration": True},
                    "declaration": {"dynamicRegistration": True, "linkSupport": True},
                    "selectionRange": {"dynamicRegistration": True},
                    "callHierarchy": {"dynamicRegistration": True},
                    "semanticTokens": {
                        "dynamicRegistration": True,
                        "tokenTypes": [
                            "namespace",
                            "type",
                            "class",
                            "enum",
                            "interface",
                            "struct",
                            "typeParameter",
                            "parameter",
                            "variable",
                            "property",
                            "enumMember",
                            "event",
                            "function",
                            "method",
                            "macro",
                            "keyword",
                            "modifier",
                            "comment",
                            "string",
                            "number",
                            "regexp",
                            "operator",
                            "decorator",
                        ],
                        "tokenModifiers": [
                            "declaration",
                            "definition",
                            "readonly",
                            "static",
                            "deprecated",
                            "abstract",
                            "async",
                            "modification",
                            "documentation",
                            "defaultLibrary",
                        ],
                        "formats": ["relative"],
                        "requests": {"range": True, "full": {"delta": True}},
                        "multilineTokenSupport": False,
                        "overlappingTokenSupport": False,
                        "serverCancelSupport": True,
                        "augmentsSyntaxTokens": True,
                    },
                    "typeHierarchy": {"dynamicRegistration": True},
                    "inlineValue": {"dynamicRegistration": True},
                    "diagnostic": {"dynamicRegistration": True, "relatedDocumentSupport": False},
                },
                "general": {
                    "staleRequestSupport": {
                        "cancel": True,
                        "retryOnContentModified": [
                            "textDocument/semanticTokens/full",
                            "textDocument/semanticTokens/range",
                            "textDocument/semanticTokens/full/delta",
                        ],
                    },
                    "regularExpressions": {"engine": "ECMAScript", "version": "ES2020"},
                    "positionEncodings": ["utf-16"],
                },
                "notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}},
            },
            "initializationOptions": {
                "bundles": ["intellicode-core.jar"],
                "settings": {
                    "java": {
                        "home": None,
                        "jdt": {
                            "ls": {
                                "java": {"home": None},
                                "vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx1G -Xms100m -Xlog:disable",
                                "lombokSupport": {"enabled": True},
                                "protobufSupport": {"enabled": True},
                                "androidSupport": {"enabled": True},
                            }
                        },
                        "errors": {"incompleteClasspath": {"severity": "error"}},
                        "configuration": {
                            "checkProjectSettingsExclusions": False,
                            "updateBuildConfiguration": "interactive",
                            "maven": {
                                "userSettings": None,
                                "globalSettings": None,
                                "notCoveredPluginExecutionSeverity": "warning",
                                "defaultMojoExecutionAction": "ignore",
                            },
                            "workspaceCacheLimit": 90,
                            "runtimes": [
                                {"name": "JavaSE-21", "path": "static/vscode-java/extension/jre/21.0.7-linux-x86_64", "default": True}
                            ],
                        },
                        "trace": {"server": "verbose"},
                        "import": {
                            "maven": {
                                "enabled": True,
                                "offline": {"enabled": False},
                                "disableTestClasspathFlag": False,
                            },
                            "gradle": {
                                "enabled": True,
                                "wrapper": {"enabled": False},
                                "version": None,
                                "home": "abs(static/gradle-7.3.3)",
                                "java": {"home": "abs(static/launch_jres/21.0.7-linux-x86_64)"},
                                "offline": {"enabled": False},
                                "arguments": None,
                                "jvmArguments": None,
                                "user": {"home": None},
                                "annotationProcessing": {"enabled": True},
                            },
                            "exclusions": [
                                "**/node_modules/**",
                                "**/.metadata/**",
                                "**/archetype-resources/**",
                                "**/META-INF/maven/**",
                            ],
                            "generatesMetadataFilesAtProjectRoot": False,
                        },
                        "maven": {"downloadSources": True, "updateSnapshots": True},
                        "eclipse": {"downloadSources": True},
                        "signatureHelp": {"enabled": True, "description": {"enabled": True}},
                        "implementationsCodeLens": {"enabled": True},
                        "format": {
                            "enabled": True,
                            "settings": {"url": None, "profile": None},
                            "comments": {"enabled": True},
                            "onType": {"enabled": True},
                            "insertSpaces": True,
                            "tabSize": 4,
                        },
                        "saveActions": {"organizeImports": False},
                        "project": {
                            "referencedLibraries": ["lib/**/*.jar"],
                            "importOnFirstTimeStartup": "automatic",
                            "importHint": True,
                            "resourceFilters": ["node_modules", "\\.git"],
                            "encoding": "ignore",
                            "exportJar": {"targetPath": "${workspaceFolder}/${workspaceFolderBasename}.jar"},
                        },
                        "contentProvider": {"preferred": None},
                        "autobuild": {"enabled": True},
                        "maxConcurrentBuilds": 1,
                        "selectionRange": {"enabled": True},
                        "showBuildStatusOnStart": {"enabled": "notification"},
                        "server": {"launchMode": "Standard"},
                        "sources": {"organizeImports": {"starThreshold": 99, "staticStarThreshold": 99}},
                        "imports": {"gradle": {"wrapper": {"checksums": []}}},
                        "templates": {"fileHeader": [], "typeComment": []},
                        "references": {"includeAccessors": True, "includeDecompiledSources": True},
                        "typeHierarchy": {"lazyLoad": False},
                        "settings": {"url": None},
                        "symbols": {"includeSourceMethodDeclarations": False},
                        "inlayHints": {"parameterNames": {"enabled": "literals", "exclusions": []}},
                        "codeAction": {"sortMembers": {"avoidVolatileChanges": True}},
                        "compile": {
                            "nullAnalysis": {
                                "nonnull": [
                                    "javax.annotation.Nonnull",
                                    "org.eclipse.jdt.annotation.NonNull",
                                    "org.springframework.lang.NonNull",
                                ],
                                "nullable": [
                                    "javax.annotation.Nullable",
                                    "org.eclipse.jdt.annotation.Nullable",
                                    "org.springframework.lang.Nullable",
                                ],
                                "mode": "automatic",
                            }
                        },
                        "sharedIndexes": {"enabled": "auto", "location": ""},
                        "silentNotification": False,
                        "dependency": {
                            "showMembers": False,
                            "syncWithFolderExplorer": True,
                            "autoRefresh": True,
                            "refreshDelay": 2000,
                            "packagePresentation": "flat",
                        },
                        "help": {"firstView": "auto", "showReleaseNotes": True, "collectErrorLog": False},
                        "test": {"defaultConfig": "", "config": {}},
                    }
                },
            },
            "trace": "verbose",
            "processId": os.getpid(),
            "workspaceFolders": [
                {
                    "uri": repo_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }

        initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri]
        bundles = [self.runtime_dependency_paths.intellicode_jar_path]
        initialize_params["initializationOptions"]["bundles"] = bundles
        initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [
            {"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True}
        ]

        for runtime in initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]:
            assert "name" in runtime
            assert "path" in runtime
            assert os.path.exists(runtime["path"]), f"Runtime required for eclipse_jdtls at path {runtime['path']} does not exist"

        gradle_settings = initialize_params["initializationOptions"]["settings"]["java"]["import"]["gradle"]
        gradle_settings["home"] = self.runtime_dependency_paths.gradle_path
        gradle_settings["java"]["home"] = self.runtime_dependency_paths.jre_path
        return initialize_params

    def _start_server(self):
        """
        Starts the Eclipse JDTLS Language Server
        """

        def register_capability_handler(params):
            assert "registrations" in params
            for registration in params["registrations"]:
                if registration["method"] == "textDocument/completion":
                    assert registration["registerOptions"]["resolveProvider"] == True
                    assert registration["registerOptions"]["triggerCharacters"] == [
                        ".",
                        "@",
                        "#",
                        "*",
                        " ",
                    ]
                    self.completions_available.set()
                if registration["method"] == "workspace/executeCommand":
                    if "java.intellicode.enable" in registration["registerOptions"]["commands"]:
                        self.intellicode_enable_command_available.set()
            return

        def lang_status_handler(params):
            # TODO: Should we wait for
            # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
            # Before proceeding?
            if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
                self.service_ready_event.set()

        def execute_client_command_handler(params):
            assert params["command"] == "_java.reloadBundles.command"
            assert params["arguments"] == []
            return []

        def window_log_message(msg):
            self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)

        def do_nothing(params):
            return

        self.server.on_request("client/registerCapability", register_capability_handler)
        self.server.on_notification("language/status", lang_status_handler)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
        self.server.on_notification("language/actionableNotification", do_nothing)

        self.logger.log("Starting EclipseJDTLS server process", logging.INFO)
        self.server.start()
        initialize_params = self._get_initialize_params(self.repository_root_path)

        self.logger.log(
            "Sending initialize request from LSP client to LSP server and awaiting response",
            logging.INFO,
        )
        init_response = self.server.send.initialize(initialize_params)
        assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
        assert "completionProvider" not in init_response["capabilities"]
        assert "executeCommandProvider" not in init_response["capabilities"]

        self.server.notify.initialized({})

        self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]})

        self.intellicode_enable_command_available.wait()

        java_intellisense_members_path = self.runtime_dependency_paths.intellisense_members_path
        assert os.path.exists(java_intellisense_members_path)
        intellicode_enable_result = self.server.send.execute_command(
            {
                "command": "java.intellicode.enable",
                "arguments": [True, java_intellisense_members_path],
            }
        )
        assert intellicode_enable_result

        # TODO: Add comments about why we wait here, and how this can be optimized
        self.service_ready_event.wait()

```

--------------------------------------------------------------------------------
/src/serena/cli.py:
--------------------------------------------------------------------------------

```python
import glob
import json
import os
import shutil
import subprocess
import sys
from logging import Logger
from pathlib import Path
from typing import Any, Literal

import click
from sensai.util import logging
from sensai.util.logging import FileLoggerContext, datetime_tag
from tqdm import tqdm

from serena.agent import SerenaAgent
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
from serena.config.serena_config import ProjectConfig, SerenaConfig, SerenaPaths
from serena.constants import (
    DEFAULT_CONTEXT,
    DEFAULT_MODES,
    PROMPT_TEMPLATES_DIR_IN_USER_HOME,
    PROMPT_TEMPLATES_DIR_INTERNAL,
    SERENA_LOG_FORMAT,
    SERENA_MANAGED_DIR_IN_HOME,
    SERENAS_OWN_CONTEXT_YAMLS_DIR,
    SERENAS_OWN_MODE_YAMLS_DIR,
    USER_CONTEXT_YAMLS_DIR,
    USER_MODE_YAMLS_DIR,
)
from serena.mcp import SerenaMCPFactory, SerenaMCPFactorySingleProcess
from serena.project import Project
from serena.tools import FindReferencingSymbolsTool, FindSymbolTool, GetSymbolsOverviewTool, SearchForPatternTool, ToolRegistry
from serena.util.logging import MemoryLogHandler
from solidlsp.ls_config import Language
from solidlsp.util.subprocess_util import subprocess_kwargs

log = logging.getLogger(__name__)

# --------------------- Utilities -------------------------------------


def _open_in_editor(path: str) -> None:
    """Open the given file in the system's default editor or viewer."""
    editor = os.environ.get("EDITOR")
    run_kwargs = subprocess_kwargs()
    try:
        if editor:
            subprocess.run([editor, path], check=False, **run_kwargs)
        elif sys.platform.startswith("win"):
            try:
                os.startfile(path)
            except OSError:
                subprocess.run(["notepad.exe", path], check=False, **run_kwargs)
        elif sys.platform == "darwin":
            subprocess.run(["open", path], check=False, **run_kwargs)
        else:
            subprocess.run(["xdg-open", path], check=False, **run_kwargs)
    except Exception as e:
        print(f"Failed to open {path}: {e}")


class ProjectType(click.ParamType):
    """ParamType allowing either a project name or a path to a project directory."""

    name = "[PROJECT_NAME|PROJECT_PATH]"

    def convert(self, value: str, param: Any, ctx: Any) -> str:
        path = Path(value).resolve()
        if path.exists() and path.is_dir():
            return str(path)
        return value


PROJECT_TYPE = ProjectType()


class AutoRegisteringGroup(click.Group):
    """
    A click.Group subclass that automatically registers any click.Command
    attributes defined on the class into the group.

    After initialization, it inspects its own class for attributes that are
    instances of click.Command (typically created via @click.command) and
    calls self.add_command(cmd) on each. This lets you define your commands
    as static methods on the subclass for IDE-friendly organization without
    manual registration.
    """

    def __init__(self, name: str, help: str):
        super().__init__(name=name, help=help)
        # Scan class attributes for click.Command instances and register them.
        for attr in dir(self.__class__):
            cmd = getattr(self.__class__, attr)
            if isinstance(cmd, click.Command):
                self.add_command(cmd)


class TopLevelCommands(AutoRegisteringGroup):
    """Root CLI group containing the core Serena commands."""

    def __init__(self) -> None:
        super().__init__(name="serena", help="Serena CLI commands. You can run `<command> --help` for more info on each command.")

    @staticmethod
    @click.command("start-mcp-server", help="Starts the Serena MCP server.")
    @click.option("--project", "project", type=PROJECT_TYPE, default=None, help="Path or name of project to activate at startup.")
    @click.option("--project-file", "project", type=PROJECT_TYPE, default=None, help="[DEPRECATED] Use --project instead.")
    @click.argument("project_file_arg", type=PROJECT_TYPE, required=False, default=None, metavar="")
    @click.option(
        "--context", type=str, default=DEFAULT_CONTEXT, show_default=True, help="Built-in context name or path to custom context YAML."
    )
    @click.option(
        "--mode",
        "modes",
        type=str,
        multiple=True,
        default=DEFAULT_MODES,
        show_default=True,
        help="Built-in mode names or paths to custom mode YAMLs.",
    )
    @click.option(
        "--transport",
        type=click.Choice(["stdio", "sse", "streamable-http"]),
        default="stdio",
        show_default=True,
        help="Transport protocol.",
    )
    @click.option("--host", type=str, default="0.0.0.0", show_default=True)
    @click.option("--port", type=int, default=8000, show_default=True)
    @click.option("--enable-web-dashboard", type=bool, is_flag=False, default=None, help="Override dashboard setting in config.")
    @click.option("--enable-gui-log-window", type=bool, is_flag=False, default=None, help="Override GUI log window setting in config.")
    @click.option(
        "--log-level",
        type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
        default=None,
        help="Override log level in config.",
    )
    @click.option("--trace-lsp-communication", type=bool, is_flag=False, default=None, help="Whether to trace LSP communication.")
    @click.option("--tool-timeout", type=float, default=None, help="Override tool execution timeout in config.")
    def start_mcp_server(
        project: str | None,
        project_file_arg: str | None,
        context: str,
        modes: tuple[str, ...],
        transport: Literal["stdio", "sse", "streamable-http"],
        host: str,
        port: int,
        enable_web_dashboard: bool | None,
        enable_gui_log_window: bool | None,
        log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None,
        trace_lsp_communication: bool | None,
        tool_timeout: float | None,
    ) -> None:
        # initialize logging, using INFO level initially (will later be adjusted by SerenaAgent according to the config)
        #   * memory log handler (for use by GUI/Dashboard)
        #   * stream handler for stderr (for direct console output, which will also be captured by clients like Claude Desktop)
        #   * file handler
        # (Note that stdout must never be used for logging, as it is used by the MCP server to communicate with the client.)
        Logger.root.setLevel(logging.INFO)
        formatter = logging.Formatter(SERENA_LOG_FORMAT)
        memory_log_handler = MemoryLogHandler()
        Logger.root.addHandler(memory_log_handler)
        stderr_handler = logging.StreamHandler(stream=sys.stderr)
        stderr_handler.formatter = formatter
        Logger.root.addHandler(stderr_handler)
        log_path = SerenaPaths().get_next_log_file_path("mcp")
        file_handler = logging.FileHandler(log_path, mode="w")
        file_handler.formatter = formatter
        Logger.root.addHandler(file_handler)

        log.info("Initializing Serena MCP server")
        log.info("Storing logs in %s", log_path)
        project_file = project_file_arg or project
        factory = SerenaMCPFactorySingleProcess(context=context, project=project_file, memory_log_handler=memory_log_handler)
        server = factory.create_mcp_server(
            host=host,
            port=port,
            modes=modes,
            enable_web_dashboard=enable_web_dashboard,
            enable_gui_log_window=enable_gui_log_window,
            log_level=log_level,
            trace_lsp_communication=trace_lsp_communication,
            tool_timeout=tool_timeout,
        )
        if project_file_arg:
            log.warning(
                "Positional project arg is deprecated; use --project instead. Used: %s",
                project_file,
            )
        log.info("Starting MCP server …")
        server.run(transport=transport)

    @staticmethod
    @click.command("print-system-prompt", help="Print the system prompt for a project.")
    @click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
    @click.option(
        "--log-level",
        type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
        default="WARNING",
        help="Log level for prompt generation.",
    )
    @click.option("--only-instructions", is_flag=True, help="Print only the initial instructions, without prefix/postfix.")
    @click.option(
        "--context", type=str, default=DEFAULT_CONTEXT, show_default=True, help="Built-in context name or path to custom context YAML."
    )
    @click.option(
        "--mode",
        "modes",
        type=str,
        multiple=True,
        default=DEFAULT_MODES,
        show_default=True,
        help="Built-in mode names or paths to custom mode YAMLs.",
    )
    def print_system_prompt(project: str, log_level: str, only_instructions: bool, context: str, modes: tuple[str, ...]) -> None:
        prefix = "You will receive access to Serena's symbolic tools. Below are instructions for using them, take them into account."
        postfix = "You begin by acknowledging that you understood the above instructions and are ready to receive tasks."
        from serena.tools.workflow_tools import InitialInstructionsTool

        lvl = logging.getLevelNamesMapping()[log_level.upper()]
        logging.configure(level=lvl)
        context_instance = SerenaAgentContext.load(context)
        mode_instances = [SerenaAgentMode.load(mode) for mode in modes]
        agent = SerenaAgent(
            project=os.path.abspath(project),
            serena_config=SerenaConfig(web_dashboard=False, log_level=lvl),
            context=context_instance,
            modes=mode_instances,
        )
        tool = agent.get_tool(InitialInstructionsTool)
        instr = tool.apply()
        if only_instructions:
            print(instr)
        else:
            print(f"{prefix}\n{instr}\n{postfix}")


class ModeCommands(AutoRegisteringGroup):
    """Group for 'mode' subcommands."""

    def __init__(self) -> None:
        super().__init__(name="mode", help="Manage Serena modes. You can run `mode <command> --help` for more info on each command.")

    @staticmethod
    @click.command("list", help="List available modes.")
    def list() -> None:
        mode_names = SerenaAgentMode.list_registered_mode_names()
        max_len_name = max(len(name) for name in mode_names) if mode_names else 20
        for name in mode_names:
            mode_yml_path = SerenaAgentMode.get_path(name)
            is_internal = Path(mode_yml_path).is_relative_to(SERENAS_OWN_MODE_YAMLS_DIR)
            descriptor = "(internal)" if is_internal else f"(at {mode_yml_path})"
            name_descr_string = f"{name:<{max_len_name + 4}}{descriptor}"
            click.echo(name_descr_string)

    @staticmethod
    @click.command("create", help="Create a new mode or copy an internal one.")
    @click.option(
        "--name",
        "-n",
        type=str,
        default=None,
        help="Name for the new mode. If --from-internal is passed may be left empty to create a mode of the same name, which will then override the internal mode.",
    )
    @click.option("--from-internal", "from_internal", type=str, default=None, help="Copy from an internal mode.")
    def create(name: str, from_internal: str) -> None:
        if not (name or from_internal):
            raise click.UsageError("Provide at least one of --name or --from-internal.")
        mode_name = name or from_internal
        dest = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
        src = (
            os.path.join(SERENAS_OWN_MODE_YAMLS_DIR, f"{from_internal}.yml")
            if from_internal
            else os.path.join(SERENAS_OWN_MODE_YAMLS_DIR, "mode.template.yml")
        )
        if not os.path.exists(src):
            raise FileNotFoundError(
                f"Internal mode '{from_internal}' not found in {SERENAS_OWN_MODE_YAMLS_DIR}. Available modes: {SerenaAgentMode.list_registered_mode_names()}"
            )
        os.makedirs(os.path.dirname(dest), exist_ok=True)
        shutil.copyfile(src, dest)
        click.echo(f"Created mode '{mode_name}' at {dest}")
        _open_in_editor(dest)

    @staticmethod
    @click.command("edit", help="Edit a custom mode YAML file.")
    @click.argument("mode_name")
    def edit(mode_name: str) -> None:
        path = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
        if not os.path.exists(path):
            if mode_name in SerenaAgentMode.list_registered_mode_names(include_user_modes=False):
                click.echo(
                    f"Mode '{mode_name}' is an internal mode and cannot be edited directly. "
                    f"Use 'mode create --from-internal {mode_name}' to create a custom mode that overrides it before editing."
                )
            else:
                click.echo(f"Custom mode '{mode_name}' not found. Create it with: mode create --name {mode_name}.")
            return
        _open_in_editor(path)

    @staticmethod
    @click.command("delete", help="Delete a custom mode file.")
    @click.argument("mode_name")
    def delete(mode_name: str) -> None:
        path = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
        if not os.path.exists(path):
            click.echo(f"Custom mode '{mode_name}' not found.")
            return
        os.remove(path)
        click.echo(f"Deleted custom mode '{mode_name}'.")


class ContextCommands(AutoRegisteringGroup):
    """Group for 'context' subcommands."""

    def __init__(self) -> None:
        super().__init__(
            name="context", help="Manage Serena contexts. You can run `context <command> --help` for more info on each command."
        )

    @staticmethod
    @click.command("list", help="List available contexts.")
    def list() -> None:
        context_names = SerenaAgentContext.list_registered_context_names()
        max_len_name = max(len(name) for name in context_names) if context_names else 20
        for name in context_names:
            context_yml_path = SerenaAgentContext.get_path(name)
            is_internal = Path(context_yml_path).is_relative_to(SERENAS_OWN_CONTEXT_YAMLS_DIR)
            descriptor = "(internal)" if is_internal else f"(at {context_yml_path})"
            name_descr_string = f"{name:<{max_len_name + 4}}{descriptor}"
            click.echo(name_descr_string)

    @staticmethod
    @click.command("create", help="Create a new context or copy an internal one.")
    @click.option(
        "--name",
        "-n",
        type=str,
        default=None,
        help="Name for the new context. If --from-internal is passed may be left empty to create a context of the same name, which will then override the internal context",
    )
    @click.option("--from-internal", "from_internal", type=str, default=None, help="Copy from an internal context.")
    def create(name: str, from_internal: str) -> None:
        if not (name or from_internal):
            raise click.UsageError("Provide at least one of --name or --from-internal.")
        ctx_name = name or from_internal
        dest = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{ctx_name}.yml")
        src = (
            os.path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, f"{from_internal}.yml")
            if from_internal
            else os.path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, "context.template.yml")
        )
        if not os.path.exists(src):
            raise FileNotFoundError(
                f"Internal context '{from_internal}' not found in {SERENAS_OWN_CONTEXT_YAMLS_DIR}. Available contexts: {SerenaAgentContext.list_registered_context_names()}"
            )
        os.makedirs(os.path.dirname(dest), exist_ok=True)
        shutil.copyfile(src, dest)
        click.echo(f"Created context '{ctx_name}' at {dest}")
        _open_in_editor(dest)

    @staticmethod
    @click.command("edit", help="Edit a custom context YAML file.")
    @click.argument("context_name")
    def edit(context_name: str) -> None:
        path = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{context_name}.yml")
        if not os.path.exists(path):
            if context_name in SerenaAgentContext.list_registered_context_names(include_user_contexts=False):
                click.echo(
                    f"Context '{context_name}' is an internal context and cannot be edited directly. "
                    f"Use 'context create --from-internal {context_name}' to create a custom context that overrides it before editing."
                )
            else:
                click.echo(f"Custom context '{context_name}' not found. Create it with: context create --name {context_name}.")
            return
        _open_in_editor(path)

    @staticmethod
    @click.command("delete", help="Delete a custom context file.")
    @click.argument("context_name")
    def delete(context_name: str) -> None:
        path = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{context_name}.yml")
        if not os.path.exists(path):
            click.echo(f"Custom context '{context_name}' not found.")
            return
        os.remove(path)
        click.echo(f"Deleted custom context '{context_name}'.")


class SerenaConfigCommands(AutoRegisteringGroup):
    """Group for 'config' subcommands."""

    def __init__(self) -> None:
        super().__init__(name="config", help="Manage Serena configuration.")

    @staticmethod
    @click.command(
        "edit", help="Edit serena_config.yml in your default editor. Will create a config file from the template if no config is found."
    )
    def edit() -> None:
        config_path = os.path.join(SERENA_MANAGED_DIR_IN_HOME, "serena_config.yml")
        if not os.path.exists(config_path):
            SerenaConfig.generate_config_file(config_path)
        _open_in_editor(config_path)


class ProjectCommands(AutoRegisteringGroup):
    """Group for 'project' subcommands."""

    def __init__(self) -> None:
        super().__init__(
            name="project", help="Manage Serena projects. You can run `project <command> --help` for more info on each command."
        )

    @staticmethod
    @click.command("generate-yml", help="Generate a project.yml file.")
    @click.argument("project_path", type=click.Path(exists=True, file_okay=False), default=os.getcwd())
    @click.option("--language", type=str, default=None, help="Programming language; inferred if not specified.")
    def generate_yml(project_path: str, language: str | None = None) -> None:
        yml_path = os.path.join(project_path, ProjectConfig.rel_path_to_project_yml())
        if os.path.exists(yml_path):
            raise FileExistsError(f"Project file {yml_path} already exists.")
        lang_inst = None
        if language:
            try:
                lang_inst = Language[language.upper()]
            except KeyError:
                all_langs = [l.name.lower() for l in Language.iter_all(include_experimental=True)]
                raise ValueError(f"Unknown language '{language}'. Supported: {all_langs}")
        generated_conf = ProjectConfig.autogenerate(project_root=project_path, project_language=lang_inst)
        print(f"Generated project.yml with language {generated_conf.language.value} at {yml_path}.")

    @staticmethod
    @click.command("index", help="Index a project by saving symbols to the LSP cache.")
    @click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
    @click.option(
        "--log-level",
        type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
        default="WARNING",
        help="Log level for indexing.",
    )
    @click.option("--timeout", type=float, default=10, help="Timeout for indexing a single file.")
    def index(project: str, log_level: str, timeout: float) -> None:
        ProjectCommands._index_project(project, log_level, timeout=timeout)

    @staticmethod
    @click.command("index-deprecated", help="Deprecated alias for 'serena project index'.")
    @click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
    @click.option("--log-level", type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), default="WARNING")
    @click.option("--timeout", type=float, default=10, help="Timeout for indexing a single file.")
    def index_deprecated(project: str, log_level: str, timeout: float) -> None:
        click.echo("Deprecated! Use `serena project index` instead.")
        ProjectCommands._index_project(project, log_level, timeout=timeout)

    @staticmethod
    def _index_project(project: str, log_level: str, timeout: float) -> None:
        lvl = logging.getLevelNamesMapping()[log_level.upper()]
        logging.configure(level=lvl)
        serena_config = SerenaConfig.from_config_file()
        proj = Project.load(os.path.abspath(project))
        click.echo(f"Indexing symbols in project {project}…")
        ls = proj.create_language_server(log_level=lvl, ls_timeout=timeout, ls_specific_settings=serena_config.ls_specific_settings)
        log_file = os.path.join(project, ".serena", "logs", "indexing.txt")

        collected_exceptions: list[Exception] = []
        files_failed = []
        with ls.start_server():
            files = proj.gather_source_files()
            for i, f in enumerate(tqdm(files, desc="Indexing")):
                try:
                    ls.request_document_symbols(f, include_body=False)
                    ls.request_document_symbols(f, include_body=True)
                except Exception as e:
                    log.error(f"Failed to index {f}, continuing.")
                    collected_exceptions.append(e)
                    files_failed.append(f)
                if (i + 1) % 10 == 0:
                    ls.save_cache()
            ls.save_cache()
        click.echo(f"Symbols saved to {ls.cache_path}")
        if len(files_failed) > 0:
            os.makedirs(os.path.dirname(log_file), exist_ok=True)
            with open(log_file, "w") as f:
                for file, exception in zip(files_failed, collected_exceptions, strict=True):
                    f.write(f"{file}\n")
                    f.write(f"{exception}\n")
            click.echo(f"Failed to index {len(files_failed)} files, see:\n{log_file}")

    @staticmethod
    @click.command("is_ignored_path", help="Check if a path is ignored by the project configuration.")
    @click.argument("path", type=click.Path(exists=False, file_okay=True, dir_okay=True))
    @click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
    def is_ignored_path(path: str, project: str) -> None:
        """
        Check if a given path is ignored by the project configuration.

        :param path: The path to check.
        :param project: The path to the project directory, defaults to the current working directory.
        """
        proj = Project.load(os.path.abspath(project))
        if os.path.isabs(path):
            path = os.path.relpath(path, start=proj.project_root)
        is_ignored = proj.is_ignored_path(path)
        click.echo(f"Path '{path}' IS {'ignored' if is_ignored else 'IS NOT ignored'} by the project configuration.")

    @staticmethod
    @click.command("index-file", help="Index a single file by saving its symbols to the LSP cache.")
    @click.argument("file", type=click.Path(exists=True, file_okay=True, dir_okay=False))
    @click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
    @click.option("--verbose", "-v", is_flag=True, help="Print detailed information about the indexed symbols.")
    def index_file(file: str, project: str, verbose: bool) -> None:
        """
        Index a single file by saving its symbols to the LSP cache, useful for debugging.
        :param file: path to the file to index, must be inside the project directory.
        :param project: path to the project directory, defaults to the current working directory.
        :param verbose: if set, prints detailed information about the indexed symbols.
        """
        proj = Project.load(os.path.abspath(project))
        if os.path.isabs(file):
            file = os.path.relpath(file, start=proj.project_root)
        if proj.is_ignored_path(file, ignore_non_source_files=True):
            click.echo(f"'{file}' is ignored or declared as non-code file by the project configuration, won't index.")
            exit(1)
        ls = proj.create_language_server()
        with ls.start_server():
            symbols, _ = ls.request_document_symbols(file, include_body=False)
            ls.request_document_symbols(file, include_body=True)
            if verbose:
                click.echo(f"Symbols in file '{file}':")
                for symbol in symbols:
                    click.echo(f"  - {symbol['name']} at line {symbol['selectionRange']['start']['line']} of kind {symbol['kind']}")
            ls.save_cache()
            click.echo(f"Successfully indexed file '{file}', {len(symbols)} symbols saved to {ls.cache_path}.")

    @staticmethod
    @click.command("health-check", help="Perform a comprehensive health check of the project's tools and language server.")
    @click.argument("project", type=click.Path(exists=True, file_okay=False, dir_okay=True), default=os.getcwd())
    def health_check(project: str) -> None:
        """
        Perform a comprehensive health check of the project's tools and language server.

        :param project: path to the project directory, defaults to the current working directory.
        """
        # NOTE: completely written by Claude Code, only functionality was reviewed, not implementation
        logging.configure(level=logging.INFO)
        project_path = os.path.abspath(project)
        proj = Project.load(project_path)

        # Create log file with timestamp
        timestamp = datetime_tag()
        log_dir = os.path.join(project_path, ".serena", "logs", "health-checks")
        os.makedirs(log_dir, exist_ok=True)
        log_file = os.path.join(log_dir, f"health_check_{timestamp}.log")

        with FileLoggerContext(log_file, append=False, enabled=True):
            log.info("Starting health check for project: %s", project_path)

            try:
                # Create SerenaAgent with dashboard disabled
                log.info("Creating SerenaAgent with disabled dashboard...")
                config = SerenaConfig(gui_log_window_enabled=False, web_dashboard=False)
                agent = SerenaAgent(project=project_path, serena_config=config)
                log.info("SerenaAgent created successfully")

                # Find first non-empty file that can be analyzed
                log.info("Searching for analyzable files...")
                files = proj.gather_source_files()
                target_file = None

                for file_path in files:
                    try:
                        full_path = os.path.join(project_path, file_path)
                        if os.path.getsize(full_path) > 0:
                            target_file = file_path
                            log.info("Found analyzable file: %s", target_file)
                            break
                    except (OSError, FileNotFoundError):
                        continue

                if not target_file:
                    log.error("No analyzable files found in project")
                    click.echo("❌ Health check failed: No analyzable files found")
                    click.echo(f"Log saved to: {log_file}")
                    return

                # Get tools from agent
                overview_tool = agent.get_tool(GetSymbolsOverviewTool)
                find_symbol_tool = agent.get_tool(FindSymbolTool)
                find_refs_tool = agent.get_tool(FindReferencingSymbolsTool)
                search_pattern_tool = agent.get_tool(SearchForPatternTool)

                # Test 1: Get symbols overview
                log.info("Testing GetSymbolsOverviewTool on file: %s", target_file)
                overview_result = agent.execute_task(lambda: overview_tool.apply(target_file))
                overview_data = json.loads(overview_result)
                log.info("GetSymbolsOverviewTool returned %d symbols", len(overview_data))

                if not overview_data:
                    log.error("No symbols found in file %s", target_file)
                    click.echo("❌ Health check failed: No symbols found in target file")
                    click.echo(f"Log saved to: {log_file}")
                    return

                # Extract suitable symbol (prefer class or function over variables)
                # LSP symbol kinds: 5=class, 12=function, 6=method, 9=constructor
                preferred_kinds = [5, 12, 6, 9]  # class, function, method, constructor

                selected_symbol = None
                for symbol in overview_data:
                    if symbol.get("kind") in preferred_kinds:
                        selected_symbol = symbol
                        break

                # If no preferred symbol found, use first available
                if not selected_symbol:
                    selected_symbol = overview_data[0]
                    log.info("No class or function found, using first available symbol")

                symbol_name = selected_symbol.get("name_path", "unknown")
                symbol_kind = selected_symbol.get("kind", "unknown")
                log.info("Using symbol for testing: %s (kind: %d)", symbol_name, symbol_kind)

                # Test 2: FindSymbolTool
                log.info("Testing FindSymbolTool for symbol: %s", symbol_name)
                find_symbol_result = agent.execute_task(
                    lambda: find_symbol_tool.apply(symbol_name, relative_path=target_file, include_body=True)
                )
                find_symbol_data = json.loads(find_symbol_result)
                log.info("FindSymbolTool found %d matches for symbol %s", len(find_symbol_data), symbol_name)

                # Test 3: FindReferencingSymbolsTool
                log.info("Testing FindReferencingSymbolsTool for symbol: %s", symbol_name)
                try:
                    find_refs_result = agent.execute_task(lambda: find_refs_tool.apply(symbol_name, relative_path=target_file))
                    find_refs_data = json.loads(find_refs_result)
                    log.info("FindReferencingSymbolsTool found %d references for symbol %s", len(find_refs_data), symbol_name)
                except Exception as e:
                    log.warning("FindReferencingSymbolsTool failed for symbol %s: %s", symbol_name, str(e))
                    find_refs_data = []

                # Test 4: SearchForPatternTool to verify references
                log.info("Testing SearchForPatternTool for pattern: %s", symbol_name)
                try:
                    search_result = agent.execute_task(
                        lambda: search_pattern_tool.apply(substring_pattern=symbol_name, restrict_search_to_code_files=True)
                    )
                    search_data = json.loads(search_result)
                    pattern_matches = sum(len(matches) for matches in search_data.values())
                    log.info("SearchForPatternTool found %d pattern matches for %s", pattern_matches, symbol_name)
                except Exception as e:
                    log.warning("SearchForPatternTool failed for pattern %s: %s", symbol_name, str(e))
                    pattern_matches = 0

                # Verify tools worked as expected
                tools_working = True
                if not find_symbol_data:
                    log.error("FindSymbolTool returned no results")
                    tools_working = False

                if len(find_refs_data) == 0 and pattern_matches == 0:
                    log.warning("Both FindReferencingSymbolsTool and SearchForPatternTool found no matches - this might indicate an issue")

                log.info("Health check completed successfully")

                if tools_working:
                    click.echo("✅ Health check passed - All tools working correctly")
                else:
                    click.echo("⚠️  Health check completed with warnings - Check log for details")

            except Exception as e:
                log.exception("Health check failed with exception: %s", str(e))
                click.echo(f"❌ Health check failed: {e!s}")

            finally:
                click.echo(f"Log saved to: {log_file}")


class ToolCommands(AutoRegisteringGroup):
    """Group for 'tool' subcommands."""

    def __init__(self) -> None:
        super().__init__(
            name="tools",
            help="Commands related to Serena's tools. You can run `serena tools <command> --help` for more info on each command.",
        )

    @staticmethod
    @click.command(
        "list",
        help="Prints an overview of the tools that are active by default (not just the active ones for your project). For viewing all tools, pass `--all / -a`",
    )
    @click.option("--quiet", "-q", is_flag=True)
    @click.option("--all", "-a", "include_optional", is_flag=True, help="List all tools, including those not enabled by default.")
    @click.option("--only-optional", is_flag=True, help="List only optional tools (those not enabled by default).")
    def list(quiet: bool = False, include_optional: bool = False, only_optional: bool = False) -> None:
        tool_registry = ToolRegistry()
        if quiet:
            if only_optional:
                tool_names = tool_registry.get_tool_names_optional()
            elif include_optional:
                tool_names = tool_registry.get_tool_names()
            else:
                tool_names = tool_registry.get_tool_names_default_enabled()
            for tool_name in tool_names:
                click.echo(tool_name)
        else:
            ToolRegistry().print_tool_overview(include_optional=include_optional, only_optional=only_optional)

    @staticmethod
    @click.command(
        "description",
        help="Print the description of a tool, optionally with a specific context (the latter may modify the default description).",
    )
    @click.argument("tool_name", type=str)
    @click.option("--context", type=str, default=None, help="Context name or path to context file.")
    def description(tool_name: str, context: str | None = None) -> None:
        # Load the context
        serena_context = None
        if context:
            serena_context = SerenaAgentContext.load(context)

        agent = SerenaAgent(
            project=None,
            serena_config=SerenaConfig(web_dashboard=False, log_level=logging.INFO),
            context=serena_context,
        )
        tool = agent.get_tool_by_name(tool_name)
        mcp_tool = SerenaMCPFactory.make_mcp_tool(tool)
        click.echo(mcp_tool.description)


class PromptCommands(AutoRegisteringGroup):
    def __init__(self) -> None:
        super().__init__(name="prompts", help="Commands related to Serena's prompts that are outside of contexts and modes.")

    @staticmethod
    def _get_user_prompt_yaml_path(prompt_yaml_name: str) -> str:
        os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
        return os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, prompt_yaml_name)

    @staticmethod
    @click.command("list", help="Lists yamls that are used for defining prompts.")
    def list() -> None:
        serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
        for prompt_yaml_name in serena_prompt_yaml_names:
            user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
            if os.path.exists(user_prompt_yaml_path):
                click.echo(f"{user_prompt_yaml_path} merged with default prompts in {prompt_yaml_name}")
            else:
                click.echo(prompt_yaml_name)

    @staticmethod
    @click.command("create-override", help="Create an override of an internal prompts yaml for customizing Serena's prompts")
    @click.argument("prompt_yaml_name")
    def create_override(prompt_yaml_name: str) -> None:
        """
        :param prompt_yaml_name: The yaml name of the prompt you want to override. Call the `list` command for discovering valid prompt yaml names.
        :return:
        """
        # for convenience, we can pass names without .yml
        if not prompt_yaml_name.endswith(".yml"):
            prompt_yaml_name = prompt_yaml_name + ".yml"
        user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
        if os.path.exists(user_prompt_yaml_path):
            raise FileExistsError(f"{user_prompt_yaml_path} already exists.")
        serena_prompt_yaml_path = os.path.join(PROMPT_TEMPLATES_DIR_INTERNAL, prompt_yaml_name)
        shutil.copyfile(serena_prompt_yaml_path, user_prompt_yaml_path)
        _open_in_editor(user_prompt_yaml_path)

    @staticmethod
    @click.command("edit-override", help="Edit an existing prompt override file")
    @click.argument("prompt_yaml_name")
    def edit_override(prompt_yaml_name: str) -> None:
        """
        :param prompt_yaml_name: The yaml name of the prompt override to edit.
        :return:
        """
        # for convenience, we can pass names without .yml
        if not prompt_yaml_name.endswith(".yml"):
            prompt_yaml_name = prompt_yaml_name + ".yml"
        user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
        if not os.path.exists(user_prompt_yaml_path):
            click.echo(f"Override file '{prompt_yaml_name}' not found. Create it with: prompts create-override {prompt_yaml_name}")
            return
        _open_in_editor(user_prompt_yaml_path)

    @staticmethod
    @click.command("list-overrides", help="List existing prompt override files")
    def list_overrides() -> None:
        os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
        serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
        override_files = glob.glob(os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, "*.yml"))
        for file_path in override_files:
            if os.path.basename(file_path) in serena_prompt_yaml_names:
                click.echo(file_path)

    @staticmethod
    @click.command("delete-override", help="Delete a prompt override file")
    @click.argument("prompt_yaml_name")
    def delete_override(prompt_yaml_name: str) -> None:
        """

        :param prompt_yaml_name:  The yaml name of the prompt override to delete."
        :return:
        """
        # for convenience, we can pass names without .yml
        if not prompt_yaml_name.endswith(".yml"):
            prompt_yaml_name = prompt_yaml_name + ".yml"
        user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
        if not os.path.exists(user_prompt_yaml_path):
            click.echo(f"Override file '{prompt_yaml_name}' not found.")
            return
        os.remove(user_prompt_yaml_path)
        click.echo(f"Deleted override file '{prompt_yaml_name}'.")


# Expose groups so we can reference them in pyproject.toml
mode = ModeCommands()
context = ContextCommands()
project = ProjectCommands()
config = SerenaConfigCommands()
tools = ToolCommands()
prompts = PromptCommands()

# Expose toplevel commands for the same reason
top_level = TopLevelCommands()
start_mcp_server = top_level.start_mcp_server
index_project = project.index_deprecated

# needed for the help script to work - register all subcommands to the top-level group
for subgroup in (mode, context, project, config, tools, prompts):
    top_level.add_command(subgroup)


def get_help() -> str:
    """Retrieve the help text for the top-level Serena CLI."""
    return top_level.get_help(click.Context(top_level, info_name="serena"))

```

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

```python
"""AL Language Server implementation for Microsoft Dynamics 365 Business Central."""

import logging
import os
import pathlib
import platform
import stat
import time
import zipfile
from pathlib import Path

import requests
from overrides import override

from solidlsp.language_servers.common import quote_windows_path
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings


class ALLanguageServer(SolidLanguageServer):
    """
    Language server implementation for AL (Microsoft Dynamics 365 Business Central).

    This implementation uses the AL Language Server from the VS Code AL extension
    (ms-dynamics-smb.al). The extension must be installed or available locally.

    Key Features:
    - Automatic download of AL extension from VS Code marketplace if not present
    - Platform-specific executable detection (Windows/Linux/macOS)
    - Special initialization sequence required by AL Language Server
    - Custom AL-specific LSP commands (al/gotodefinition, al/setActiveWorkspace)
    - File opening requirement before symbol retrieval
    """

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        """
        Initialize the AL Language Server.

        Args:
            config: Language server configuration
            logger: Logger instance for debugging
            repository_root_path: Root path of the AL project (must contain app.json)
            solidlsp_settings: Solid LSP settings

        Note:
            The initialization process will automatically:
            1. Check for AL extension in the resources directory
            2. Download it from VS Code marketplace if not found
            3. Extract and configure the platform-specific executable

        """
        # Setup runtime dependencies and get the language server command
        # This will download the AL extension if needed
        cmd = self._setup_runtime_dependencies(logger, config, solidlsp_settings)

        self._project_load_check_supported: bool = True
        """Whether the AL server supports the project load status check request.
        
        Some AL server versions don't support the 'al/hasProjectClosureLoadedRequest'
        custom LSP request. This flag starts as True and is set to False if the
        request fails, preventing repeated unsuccessful attempts.
        """

        super().__init__(
            config,
            logger,
            repository_root_path,
            ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
            "al",  # Language ID for LSP
            solidlsp_settings,
        )

    @classmethod
    def _download_al_extension(cls, logger: LanguageServerLogger, url: str, target_dir: str) -> bool:
        """
        Download and extract the AL extension from VS Code marketplace.

        The VS Code marketplace packages extensions as .vsix files (which are ZIP archives).
        This method downloads the VSIX file and extracts it to get the language server binaries.

        Args:
            logger: Logger for tracking download progress
            url: VS Code marketplace URL for the AL extension
            target_dir: Directory where the extension will be extracted

        Returns:
            True if successful, False otherwise

        Note:
            The download includes progress tracking and proper user-agent headers
            to ensure compatibility with the VS Code marketplace.

        """
        try:
            logger.log(f"Downloading AL extension from {url}", logging.INFO)

            # Create target directory for the extension
            os.makedirs(target_dir, exist_ok=True)

            # Download with proper headers to mimic VS Code marketplace client
            # These headers are required for the marketplace to serve the VSIX file
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
                "Accept": "application/octet-stream, application/vsix, */*",
            }

            response = requests.get(url, headers=headers, stream=True, timeout=300)
            response.raise_for_status()

            # Save to temporary VSIX file (will be deleted after extraction)
            temp_file = os.path.join(target_dir, "al_extension_temp.vsix")
            total_size = int(response.headers.get("content-length", 0))

            logger.log(f"Downloading {total_size / 1024 / 1024:.1f} MB...", logging.INFO)

            with open(temp_file, "wb") as f:
                downloaded = 0
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
                        downloaded += len(chunk)
                        if total_size > 0 and downloaded % (10 * 1024 * 1024) == 0:  # Log progress every 10MB
                            progress = (downloaded / total_size) * 100
                            logger.log(f"Download progress: {progress:.1f}%", logging.INFO)

            logger.log("Download complete, extracting...", logging.INFO)

            # Extract VSIX file (VSIX files are just ZIP archives with a different extension)
            # This will extract the extension folder containing the language server binaries
            with zipfile.ZipFile(temp_file, "r") as zip_ref:
                zip_ref.extractall(target_dir)

            # Clean up temp file
            os.remove(temp_file)

            logger.log("AL extension extracted successfully", logging.INFO)
            return True

        except Exception as e:
            logger.log(f"Error downloading/extracting AL extension: {e}", logging.ERROR)
            return False

    @classmethod
    def _setup_runtime_dependencies(
        cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
    ) -> str:
        """
        Setup runtime dependencies for AL Language Server and return the command to start the server.

        This method handles the complete setup process:
        1. Checks for existing AL extension installations
        2. Downloads from VS Code marketplace if not found
        3. Configures executable permissions on Unix systems
        4. Returns the properly formatted command string

        The AL Language Server executable is located in different paths based on the platform:
        - Windows: bin/win32/Microsoft.Dynamics.Nav.EditorServices.Host.exe
        - Linux: bin/linux/Microsoft.Dynamics.Nav.EditorServices.Host
        - macOS: bin/darwin/Microsoft.Dynamics.Nav.EditorServices.Host
        """
        system = platform.system()

        # Find existing extension or download if needed
        extension_path = cls._find_al_extension(logger, solidlsp_settings)
        if extension_path is None:
            logger.log("AL extension not found on disk, attempting to download...", logging.INFO)
            extension_path = cls._download_and_install_al_extension(logger, solidlsp_settings)

        if extension_path is None:
            raise RuntimeError(
                "Failed to locate or download AL Language Server. Please either:\n"
                "1. Set AL_EXTENSION_PATH environment variable to the AL extension directory\n"
                "2. Install the AL extension in VS Code (ms-dynamics-smb.al)\n"
                "3. Ensure internet connection for automatic download"
            )

        # Build executable path based on platform
        executable_path = cls._get_executable_path(extension_path, system)

        if not os.path.exists(executable_path):
            raise RuntimeError(f"AL Language Server executable not found at: {executable_path}")

        # Prepare and return the executable command
        return cls._prepare_executable(executable_path, system, logger)

    @classmethod
    def _find_al_extension(cls, logger: LanguageServerLogger, solidlsp_settings: SolidLSPSettings) -> str | None:
        """
        Find AL extension in various locations.

        Search order:
        1. Environment variable (AL_EXTENSION_PATH)
        2. Default download location (~/.serena/ls_resources/al-extension)
        3. VS Code installed extensions

        Returns:
            Path to AL extension directory or None if not found

        """
        # Check environment variable
        env_path = os.environ.get("AL_EXTENSION_PATH")
        if env_path and os.path.exists(env_path):
            logger.log(f"Found AL extension via AL_EXTENSION_PATH: {env_path}", logging.DEBUG)
            return env_path
        elif env_path:
            logger.log(f"AL_EXTENSION_PATH set but directory not found: {env_path}", logging.WARNING)

        # Check default download location
        default_path = os.path.join(cls.ls_resources_dir(solidlsp_settings), "al-extension", "extension")
        if os.path.exists(default_path):
            logger.log(f"Found AL extension in default location: {default_path}", logging.DEBUG)
            return default_path

        # Search VS Code extensions
        vscode_path = cls._find_al_extension_in_vscode(logger)
        if vscode_path:
            logger.log(f"Found AL extension in VS Code: {vscode_path}", logging.DEBUG)
            return vscode_path

        logger.log("AL extension not found in any known location", logging.DEBUG)
        return None

    @classmethod
    def _download_and_install_al_extension(cls, logger: LanguageServerLogger, solidlsp_settings: SolidLSPSettings) -> str | None:
        """
        Download and install AL extension from VS Code marketplace.

        Returns:
            Path to installed extension or None if download failed

        """
        al_extension_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "al-extension")

        # AL extension version - using latest stable version
        AL_VERSION = "latest"
        url = f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-dynamics-smb/vsextensions/al/{AL_VERSION}/vspackage"

        logger.log(f"Downloading AL extension from: {url}", logging.INFO)

        if cls._download_al_extension(logger, url, al_extension_dir):
            extension_path = os.path.join(al_extension_dir, "extension")
            if os.path.exists(extension_path):
                logger.log("AL extension downloaded and installed successfully", logging.INFO)
                return extension_path
            else:
                logger.log(f"Download completed but extension not found at: {extension_path}", logging.ERROR)
        else:
            logger.log("Failed to download AL extension from marketplace", logging.ERROR)

        return None

    @classmethod
    def _get_executable_path(cls, extension_path: str, system: str) -> str:
        """
        Build platform-specific executable path.

        Args:
            extension_path: Path to AL extension directory
            system: Operating system name

        Returns:
            Full path to executable

        """
        if system == "Windows":
            return os.path.join(extension_path, "bin", "win32", "Microsoft.Dynamics.Nav.EditorServices.Host.exe")
        elif system == "Linux":
            return os.path.join(extension_path, "bin", "linux", "Microsoft.Dynamics.Nav.EditorServices.Host")
        elif system == "Darwin":
            return os.path.join(extension_path, "bin", "darwin", "Microsoft.Dynamics.Nav.EditorServices.Host")
        else:
            raise RuntimeError(f"Unsupported platform: {system}")

    @classmethod
    def _prepare_executable(cls, executable_path: str, system: str, logger: LanguageServerLogger) -> str:
        """
        Prepare the executable by setting permissions and handling path quoting.

        Args:
            executable_path: Path to the executable
            system: Operating system name
            logger: Logger instance

        Returns:
            Properly formatted command string

        """
        # Make sure executable has proper permissions on Unix-like systems
        if system in ["Linux", "Darwin"]:
            st = os.stat(executable_path)
            os.chmod(executable_path, st.st_mode | stat.S_IEXEC)
            logger.log(f"Set execute permission on: {executable_path}", logging.DEBUG)

        logger.log(f"Using AL Language Server executable: {executable_path}", logging.INFO)

        # The AL Language Server uses stdio for LSP communication by default
        # Use the utility function to handle Windows path quoting
        return quote_windows_path(executable_path)

    @classmethod
    def _get_language_server_command_fallback(cls, logger: LanguageServerLogger) -> str:
        """
        Get the command to start the AL language server.

        Returns:
            Command string to launch the AL language server

        Raises:
            RuntimeError: If AL extension cannot be found

        """
        # Check if AL extension path is configured via environment variable
        al_extension_path = os.environ.get("AL_EXTENSION_PATH")

        if not al_extension_path:
            # Try to find the extension in the current working directory
            # (for development/testing when extension is in the serena repo)
            cwd_path = Path.cwd()
            potential_extension = None

            # Look for ms-dynamics-smb.al-* directories
            for item in cwd_path.iterdir():
                if item.is_dir() and item.name.startswith("ms-dynamics-smb.al-"):
                    potential_extension = item
                    break

            if potential_extension:
                al_extension_path = str(potential_extension)
                logger.log(f"Found AL extension in current directory: {al_extension_path}", logging.DEBUG)
            else:
                # Try to find in common VS Code extension locations
                al_extension_path = cls._find_al_extension_in_vscode(logger)

        if not al_extension_path:
            raise RuntimeError(
                "AL Language Server not found. Please either:\n"
                "1. Set AL_EXTENSION_PATH environment variable to the VS Code AL extension directory\n"
                "2. Install the AL extension in VS Code (ms-dynamics-smb.al)\n"
                "3. Place the extension directory in the current working directory"
            )

        # Determine platform-specific executable
        system = platform.system()
        if system == "Windows":
            executable = os.path.join(al_extension_path, "bin", "win32", "Microsoft.Dynamics.Nav.EditorServices.Host.exe")
        elif system == "Linux":
            executable = os.path.join(al_extension_path, "bin", "linux", "Microsoft.Dynamics.Nav.EditorServices.Host")
        elif system == "Darwin":
            executable = os.path.join(al_extension_path, "bin", "darwin", "Microsoft.Dynamics.Nav.EditorServices.Host")
        else:
            raise RuntimeError(f"Unsupported platform: {system}")

        # Verify executable exists
        if not os.path.exists(executable):
            raise RuntimeError(
                f"AL Language Server executable not found at: {executable}\nPlease ensure the AL extension is properly installed."
            )

        # Make sure executable has proper permissions on Unix-like systems
        if system in ["Linux", "Darwin"]:
            st = os.stat(executable)
            os.chmod(executable, st.st_mode | stat.S_IEXEC)

        logger.log(f"Using AL Language Server executable: {executable}", logging.INFO)

        # The AL Language Server uses stdio for LSP communication (no --stdio flag needed)
        # Use the utility function to handle Windows path quoting
        return quote_windows_path(executable)

    @classmethod
    def _find_al_extension_in_vscode(cls, logger: LanguageServerLogger) -> str | None:
        """
        Try to find AL extension in common VS Code extension locations.

        Returns:
            Path to AL extension directory or None if not found

        """
        home = Path.home()
        possible_paths = []

        # Common VS Code extension paths
        if platform.system() == "Windows":
            possible_paths.extend(
                [
                    home / ".vscode" / "extensions",
                    home / ".vscode-insiders" / "extensions",
                    Path(os.environ.get("APPDATA", "")) / "Code" / "User" / "extensions",
                    Path(os.environ.get("APPDATA", "")) / "Code - Insiders" / "User" / "extensions",
                ]
            )
        else:
            possible_paths.extend(
                [
                    home / ".vscode" / "extensions",
                    home / ".vscode-server" / "extensions",
                    home / ".vscode-insiders" / "extensions",
                ]
            )

        for base_path in possible_paths:
            if base_path.exists():
                logger.log(f"Searching for AL extension in: {base_path}", logging.DEBUG)
                # Look for AL extension directories
                for item in base_path.iterdir():
                    if item.is_dir() and item.name.startswith("ms-dynamics-smb.al-"):
                        logger.log(f"Found AL extension at: {item}", logging.DEBUG)
                        return str(item)

        return None

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> dict:
        """
        Returns the initialize params for the AL Language Server.
        """
        # Ensure we have an absolute path for URI generation
        repository_path = pathlib.Path(repository_absolute_path).resolve()
        root_uri = repository_path.as_uri()

        # AL requires extensive capabilities based on VS Code trace
        initialize_params = {
            "processId": os.getpid(),
            "rootPath": str(repository_path),
            "rootUri": root_uri,
            "capabilities": {
                "workspace": {
                    "applyEdit": True,
                    "workspaceEdit": {
                        "documentChanges": True,
                        "resourceOperations": ["create", "rename", "delete"],
                        "failureHandling": "textOnlyTransactional",
                        "normalizesLineEndings": True,
                    },
                    "configuration": True,
                    "didChangeWatchedFiles": {"dynamicRegistration": True},
                    "symbol": {"dynamicRegistration": True, "symbolKind": {"valueSet": list(range(1, 27))}},
                    "executeCommand": {"dynamicRegistration": True},
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "workspaceFolders": True,
                },
                "textDocument": {
                    "synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True},
                    "completion": {
                        "dynamicRegistration": True,
                        "contextSupport": True,
                        "completionItem": {
                            "snippetSupport": True,
                            "commitCharactersSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "deprecatedSupport": True,
                            "preselectSupport": True,
                        },
                    },
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                    "definition": {"dynamicRegistration": True, "linkSupport": True},
                    "references": {"dynamicRegistration": True},
                    "documentHighlight": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                        "hierarchicalDocumentSymbolSupport": True,
                    },
                    "codeAction": {"dynamicRegistration": True},
                    "formatting": {"dynamicRegistration": True},
                    "rangeFormatting": {"dynamicRegistration": True},
                    "rename": {"dynamicRegistration": True, "prepareSupport": True},
                },
                "window": {
                    "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}},
                    "showDocument": {"support": True},
                    "workDoneProgress": True,
                },
            },
            "trace": "verbose",
            "workspaceFolders": [{"uri": root_uri, "name": repository_path.name}],
        }

        return initialize_params

    @override
    def _start_server(self):
        """
        Starts the AL Language Server process and initializes it.

        This method sets up custom notification handlers for AL-specific messages
        before starting the server. The AL server sends various notifications
        during initialization and project loading that need to be handled.
        """

        # Set up event handlers
        def do_nothing(params):
            return

        def window_log_message(msg):
            self.logger.log(f"AL LSP: window/logMessage: {msg}", logging.INFO)

        def publish_diagnostics(params):
            # AL server publishes diagnostics during initialization
            uri = params.get("uri", "")
            diagnostics = params.get("diagnostics", [])
            self.logger.log(f"AL LSP: Diagnostics for {uri}: {len(diagnostics)} issues", logging.DEBUG)

        def handle_al_notifications(params):
            # AL server sends custom notifications during project loading
            self.logger.log("AL LSP: Notification received", logging.DEBUG)

        # Register handlers for AL-specific notifications
        # These notifications are sent by the AL server during initialization and operation
        self.server.on_notification("window/logMessage", window_log_message)  # Server log messages
        self.server.on_notification("textDocument/publishDiagnostics", publish_diagnostics)  # Compilation diagnostics
        self.server.on_notification("$/progress", do_nothing)  # Progress notifications during loading
        self.server.on_notification("al/refreshExplorerObjects", handle_al_notifications)  # AL-specific object updates

        # Start the server process
        self.logger.log("Starting AL Language Server process", logging.INFO)
        self.server.start()

        # Send initialize request
        initialize_params = self._get_initialize_params(self.repository_root_path)

        self.logger.log(
            "Sending initialize request from LSP client to AL LSP server and awaiting response",
            logging.INFO,
        )

        # Send initialize and wait for response
        resp = self.server.send_request("initialize", initialize_params)
        if resp is None:
            raise RuntimeError("AL Language Server initialization failed - no response")

        self.logger.log("AL Language Server initialized successfully", logging.INFO)

        # Send initialized notification
        self.server.send_notification("initialized", {})
        self.logger.log("Sent initialized notification", logging.INFO)

    @override
    def start(self) -> "ALLanguageServer":
        """
        Start the AL Language Server with special initialization.
        """
        # Call parent start method
        super().start()

        # AL-specific post-initialization
        self._post_initialize_al_workspace()

        # Note: set_active_workspace() can be called manually if needed for multi-workspace scenarios
        # We don't call it automatically to avoid issues during single-workspace initialization

        return self

    def _post_initialize_al_workspace(self) -> None:
        """
        Post-initialization setup for AL Language Server.

        The AL server requires additional setup after initialization:
        1. Send workspace configuration - provides AL settings and paths
        2. Open app.json to trigger project loading - AL uses app.json to identify project structure
        3. Optionally wait for project to be loaded if supported

        This special initialization sequence is unique to AL and necessary for proper
        symbol resolution and navigation features.
        """
        # No sleep needed - server is already initialized

        # Send workspace configuration first
        # This tells AL about assembly paths, package caches, and code analysis settings
        try:
            self.server.send_notification(
                "workspace/didChangeConfiguration",
                {
                    "settings": {
                        "workspacePath": self.repository_root_path,
                        "alResourceConfigurationSettings": {
                            "assemblyProbingPaths": ["./.netpackages"],
                            "codeAnalyzers": [],
                            "enableCodeAnalysis": False,
                            "backgroundCodeAnalysis": "Project",
                            "packageCachePaths": ["./.alpackages"],
                            "ruleSetPath": None,
                            "enableCodeActions": True,
                            "incrementalBuild": False,
                            "outputAnalyzerStatistics": True,
                            "enableExternalRulesets": True,
                        },
                        "setActiveWorkspace": True,
                        "expectedProjectReferenceDefinitions": [],
                        "activeWorkspaceClosure": [self.repository_root_path],
                    }
                },
            )
            self.logger.log("Sent workspace configuration", logging.DEBUG)
        except Exception as e:
            self.logger.log(f"Failed to send workspace config: {e}", logging.WARNING)

        # Check if app.json exists and open it
        # app.json is the AL project manifest file (similar to package.json for Node.js)
        # Opening it triggers AL to load the project and index all AL files
        app_json_path = Path(self.repository_root_path) / "app.json"
        if app_json_path.exists():
            try:
                with open(app_json_path, encoding="utf-8") as f:
                    app_json_content = f.read()

                # Use forward slashes for URI
                app_json_uri = app_json_path.as_uri()

                # Send textDocument/didOpen for app.json
                self.server.send_notification(
                    "textDocument/didOpen",
                    {"textDocument": {"uri": app_json_uri, "languageId": "json", "version": 1, "text": app_json_content}},
                )

                self.logger.log(f"Opened app.json: {app_json_uri}", logging.DEBUG)
            except Exception as e:
                self.logger.log(f"Failed to open app.json: {e}", logging.WARNING)

        # Try to set active workspace (AL-specific custom LSP request)
        # This is optional and may not be supported by all AL server versions
        workspace_uri = Path(self.repository_root_path).resolve().as_uri()
        try:
            result = self.server.send_request(
                "al/setActiveWorkspace",
                {
                    "currentWorkspaceFolderPath": {"uri": workspace_uri, "name": Path(self.repository_root_path).name, "index": 0},
                    "settings": {
                        "workspacePath": self.repository_root_path,
                        "setActiveWorkspace": True,
                    },
                },
                timeout=2,  # Quick timeout since this is optional
            )
            self.logger.log(f"Set active workspace result: {result}", logging.DEBUG)
        except Exception as e:
            # This is a custom AL request, not critical if it fails
            self.logger.log(f"Failed to set active workspace (non-critical): {e}", logging.DEBUG)

        # Check if project supports load status check (optional)
        # Many AL server versions don't support this, so we use a short timeout
        # and continue regardless of the result
        self._wait_for_project_load(timeout=3)

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        """
        Define AL-specific directories to ignore during file scanning.

        These directories contain generated files, dependencies, or cache data
        that should not be analyzed for symbols.

        Args:
            dirname: Directory name to check

        Returns:
            True if directory should be ignored

        """
        al_ignore_dirs = {
            ".alpackages",  # AL package cache - downloaded dependencies
            ".alcache",  # AL compiler cache - intermediate compilation files
            ".altemplates",  # AL templates - code generation templates
            ".snapshots",  # Test snapshots - test result snapshots
            "out",  # Compiled output - generated .app files
            ".vscode",  # VS Code settings - editor configuration
            "Reference",  # Reference assemblies - .NET dependencies
            ".netpackages",  # .NET packages - NuGet packages for AL
            "bin",  # Binary output - compiled binaries
            "obj",  # Object files - intermediate build artifacts
        }

        # Check parent class ignore list first, then AL-specific
        return super().is_ignored_dirname(dirname) or dirname in al_ignore_dirs

    @override
    def request_full_symbol_tree(self, within_relative_path: str | None = None, include_body: bool = False) -> list[dict]:
        """
        Override to handle AL's requirement of opening files before requesting symbols.

        The AL Language Server requires files to be explicitly opened via textDocument/didOpen
        before it can provide meaningful symbols. Without this, it only returns directory symbols.
        This is different from most language servers which can provide symbols for unopened files.

        This method:
        1. Scans the repository for all AL files (.al and .dal extensions)
        2. Opens each file with the AL server
        3. Requests symbols for each file
        4. Combines all symbols into a hierarchical tree structure
        5. Closes the files to free resources

        Args:
            within_relative_path: Restrict search to this file or directory path
            include_body: Whether to include symbol body content

        Returns:
            Full symbol tree with all AL symbols from opened files organized by directory

        """
        self.logger.log("AL: Starting request_full_symbol_tree with file opening", logging.DEBUG)

        # Determine the root path for scanning
        if within_relative_path is not None:
            within_abs_path = os.path.join(self.repository_root_path, within_relative_path)
            if not os.path.exists(within_abs_path):
                raise FileNotFoundError(f"File or directory not found: {within_abs_path}")

            if os.path.isfile(within_abs_path):
                # Single file case - use parent class implementation
                _, root_nodes = self.request_document_symbols(within_relative_path, include_body=include_body)
                return root_nodes

            # Directory case - scan within this directory
            scan_root = Path(within_abs_path)
        else:
            # Scan entire repository
            scan_root = Path(self.repository_root_path)

        # For AL, we always need to open files to get symbols
        al_files = []

        # Walk through the repository to find all AL files
        for root, dirs, files in os.walk(scan_root):
            # Skip ignored directories
            dirs[:] = [d for d in dirs if not self.is_ignored_dirname(d)]

            # Find AL files
            for file in files:
                if file.endswith((".al", ".dal")):
                    file_path = Path(root) / file
                    # Use forward slashes for consistent paths
                    try:
                        relative_path = str(file_path.relative_to(self.repository_root_path)).replace("\\", "/")
                        al_files.append((file_path, relative_path))
                    except ValueError:
                        # File is outside repository root, skip it
                        continue

        self.logger.log(f"AL: Found {len(al_files)} AL files", logging.DEBUG)

        if not al_files:
            self.logger.log("AL: No AL files found in repository", logging.WARNING)
            return []

        # Collect all symbols from all files
        all_file_symbols = []

        for file_path, relative_path in al_files:
            try:
                # Use our overridden request_document_symbols which handles opening
                self.logger.log(f"AL: Getting symbols for {relative_path}", logging.DEBUG)
                all_syms, root_syms = self.request_document_symbols(relative_path, include_body=include_body)

                if root_syms:
                    # Create a file-level symbol containing the document symbols
                    file_symbol = {
                        "name": file_path.stem,  # Just the filename without extension
                        "kind": 1,  # File
                        "children": root_syms,
                        "location": {
                            "uri": file_path.as_uri(),
                            "relativePath": relative_path,
                            "absolutePath": str(file_path),
                            "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}},
                        },
                    }
                    all_file_symbols.append(file_symbol)
                    self.logger.log(f"AL: Added {len(root_syms)} symbols from {relative_path}", logging.DEBUG)
                elif all_syms:
                    # If we only got all_syms but not root, use all_syms
                    file_symbol = {
                        "name": file_path.stem,
                        "kind": 1,  # File
                        "children": all_syms,
                        "location": {
                            "uri": file_path.as_uri(),
                            "relativePath": relative_path,
                            "absolutePath": str(file_path),
                            "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}},
                        },
                    }
                    all_file_symbols.append(file_symbol)
                    self.logger.log(f"AL: Added {len(all_syms)} symbols from {relative_path}", logging.DEBUG)

            except Exception as e:
                self.logger.log(f"AL: Failed to get symbols for {relative_path}: {e}", logging.WARNING)

        if all_file_symbols:
            self.logger.log(f"AL: Returning symbols from {len(all_file_symbols)} files", logging.DEBUG)

            # Group files by directory
            directory_structure = {}

            for file_symbol in all_file_symbols:
                rel_path = file_symbol["location"]["relativePath"]
                path_parts = rel_path.split("/")

                if len(path_parts) > 1:
                    # File is in a subdirectory
                    dir_path = "/".join(path_parts[:-1])
                    if dir_path not in directory_structure:
                        directory_structure[dir_path] = []
                    directory_structure[dir_path].append(file_symbol)
                else:
                    # File is in root
                    if "." not in directory_structure:
                        directory_structure["."] = []
                    directory_structure["."].append(file_symbol)

            # Build hierarchical structure
            result = []
            repo_path = Path(self.repository_root_path)
            for dir_path, file_symbols in directory_structure.items():
                if dir_path == ".":
                    # Root level files
                    result.extend(file_symbols)
                else:
                    # Create directory symbol
                    dir_symbol = {
                        "name": Path(dir_path).name,
                        "kind": 4,  # Package/Directory
                        "children": file_symbols,
                        "location": {
                            "relativePath": dir_path,
                            "absolutePath": str(repo_path / dir_path),
                            "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}},
                        },
                    }
                    result.append(dir_symbol)

            return result
        else:
            self.logger.log("AL: No symbols found in any files", logging.WARNING)
            return []

    # ===== Phase 1: Custom AL Command Implementations =====

    @override
    def _send_definition_request(self, definition_params: DefinitionParams) -> Definition | list[LocationLink] | None:
        """
        Override to use AL's custom gotodefinition command.

        AL Language Server uses 'al/gotodefinition' instead of the standard
        'textDocument/definition' request. This custom command provides better
        navigation for AL-specific constructs like table extensions, page extensions,
        and codeunit references.

        If the custom command fails, we fall back to the standard LSP method.
        """
        # Convert standard params to AL format (same structure, different method)
        al_params = {"textDocument": definition_params["textDocument"], "position": definition_params["position"]}

        try:
            # Use custom AL command instead of standard LSP
            response = self.server.send_request("al/gotodefinition", al_params)
            self.logger.log(f"AL gotodefinition response: {response}", logging.DEBUG)
            return response
        except Exception as e:
            self.logger.log(f"Failed to use al/gotodefinition, falling back to standard: {e}", logging.WARNING)
            # Fallback to standard LSP method if custom command fails
            return super()._send_definition_request(definition_params)

    def check_project_loaded(self) -> bool:
        """
        Check if AL project closure is fully loaded.

        Uses AL's custom 'al/hasProjectClosureLoadedRequest' to determine if
        the project and all its dependencies have been fully loaded and indexed.
        This is important because AL operations may fail or return incomplete
        results if the project is still loading.

        Returns:
            bool: True if project is loaded, False otherwise

        """
        if not hasattr(self, "server") or not self.server_started:
            self.logger.log("Cannot check project load - server not started", logging.DEBUG)
            return False

        # Check if we've already determined this request isn't supported
        if not self._project_load_check_supported:
            return True  # Assume loaded if check isn't supported

        try:
            # Use a very short timeout since this is just a status check
            response = self.server.send_request("al/hasProjectClosureLoadedRequest", {}, timeout=1)
            # Response can be boolean directly, dict with 'loaded' field, or None
            if isinstance(response, bool):
                return response
            elif isinstance(response, dict):
                return response.get("loaded", False)
            elif response is None:
                # None typically means the project is still loading
                self.logger.log("Project load check returned None", logging.DEBUG)
                return False
            else:
                self.logger.log(f"Unexpected response type for project load check: {type(response)}", logging.DEBUG)
                return False
        except Exception as e:
            # Mark as unsupported to avoid repeated failed attempts
            self._project_load_check_supported = False
            self.logger.log(f"Project load check not supported by this AL server version: {e}", logging.DEBUG)
            # Assume loaded if we can't check
            return True

    def _wait_for_project_load(self, timeout: int = 3) -> bool:
        """
        Wait for project to be fully loaded.

        Polls the AL server to check if the project is loaded.
        This is optional as not all AL server versions support this check.
        We use a short timeout and continue regardless of the result.

        Args:
            timeout: Maximum time to wait in seconds (default 3s)

        Returns:
            bool: True if project loaded within timeout, False otherwise

        """
        start_time = time.time()
        self.logger.log(f"Checking AL project load status (timeout: {timeout}s)...", logging.DEBUG)

        while time.time() - start_time < timeout:
            if self.check_project_loaded():
                elapsed = time.time() - start_time
                self.logger.log(f"AL project fully loaded after {elapsed:.1f}s", logging.INFO)
                return True
            time.sleep(0.5)

        self.logger.log(f"Project load check timed out after {timeout}s (non-critical)", logging.DEBUG)
        return False

    def set_active_workspace(self, workspace_uri: str | None = None) -> None:
        """
        Set the active AL workspace.

        This is important when multiple workspaces exist to ensure operations
        target the correct workspace. The AL server can handle multiple projects
        simultaneously, but only one can be "active" at a time for operations
        like symbol search and navigation.

        This uses the custom 'al/setActiveWorkspace' LSP command.

        Args:
            workspace_uri: URI of workspace to set as active, or None to use repository root

        """
        if not hasattr(self, "server") or not self.server_started:
            self.logger.log("Cannot set active workspace - server not started", logging.DEBUG)
            return

        if workspace_uri is None:
            workspace_uri = Path(self.repository_root_path).resolve().as_uri()

        params = {"workspaceUri": workspace_uri}

        try:
            self.server.send_request("al/setActiveWorkspace", params)
            self.logger.log(f"Set active workspace to: {workspace_uri}", logging.INFO)
        except Exception as e:
            self.logger.log(f"Failed to set active workspace: {e}", logging.WARNING)
            # Non-critical error, continue operation

```
Page 9/12FirstPrevNextLast