#
tokens: 48680/50000 18/294 files (page 4/12)
lines: off (toggle) GitHub
raw markdown copy
This is page 4 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
│       │   ├── 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
│   │       ├── 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
│       ├── 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

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

```python
"""Tests for the mcp.py module in serena."""

import pytest
from mcp.server.fastmcp.tools.base import Tool as MCPTool

from serena.agent import Tool, ToolRegistry
from serena.config.context_mode import SerenaAgentContext
from serena.mcp import SerenaMCPFactory

make_tool = SerenaMCPFactory.make_mcp_tool


# Create a mock agent for tool initialization
class MockAgent:
    def __init__(self):
        self.project_config = None
        self.serena_config = None

    @staticmethod
    def get_context() -> SerenaAgentContext:
        return SerenaAgentContext.load_default()


class BaseMockTool(Tool):
    """A mock Tool class for testing."""

    def __init__(self):
        super().__init__(MockAgent())  # type: ignore


class BasicTool(BaseMockTool):
    """A mock Tool class for testing."""

    def apply(self, name: str, age: int = 0) -> str:
        """This is a test function.

        :param name: The person's name
        :param age: The person's age
        :return: A greeting message
        """
        return f"Hello {name}, you are {age} years old!"

    def apply_ex(
        self,
        log_call: bool = True,
        catch_exceptions: bool = True,
        **kwargs,
    ) -> str:
        """Mock implementation of apply_ex."""
        return self.apply(**kwargs)


def test_make_tool_basic() -> None:
    """Test that make_tool correctly creates an MCP tool from a Tool object."""
    mock_tool = BasicTool()

    mcp_tool = make_tool(mock_tool)

    # Test that the MCP tool has the correct properties
    assert isinstance(mcp_tool, MCPTool)
    assert mcp_tool.name == "basic"
    assert "This is a test function. Returns A greeting message." in mcp_tool.description

    # Test that the parameters were correctly processed
    parameters = mcp_tool.parameters
    assert "properties" in parameters
    assert "name" in parameters["properties"]
    assert "age" in parameters["properties"]
    assert parameters["properties"]["name"]["description"] == "The person's name."
    assert parameters["properties"]["age"]["description"] == "The person's age."


def test_make_tool_execution() -> None:
    """Test that the execution function created by make_tool works correctly."""
    mock_tool = BasicTool()
    mcp_tool = make_tool(mock_tool)

    # Execute the MCP tool function
    result = mcp_tool.fn(name="Alice", age=30)

    assert result == "Hello Alice, you are 30 years old!"


def test_make_tool_no_params() -> None:
    """Test make_tool with a function that has no parameters."""

    class NoParamsTool(BaseMockTool):
        def apply(self) -> str:
            """This is a test function with no parameters.

            :return: A simple result
            """
            return "Simple result"

        def apply_ex(self, *args, **kwargs) -> str:
            return self.apply()

    tool = NoParamsTool()
    mcp_tool = make_tool(tool)

    assert mcp_tool.name == "no_params"
    assert "This is a test function with no parameters. Returns A simple result." in mcp_tool.description
    assert mcp_tool.parameters["properties"] == {}


def test_make_tool_no_return_description() -> None:
    """Test make_tool with a function that has no return description."""

    class NoReturnTool(BaseMockTool):
        def apply(self, param: str) -> str:
            """This is a test function.

            :param param: The parameter
            """
            return f"Processed: {param}"

        def apply_ex(self, *args, **kwargs) -> str:
            return self.apply(**kwargs)

    tool = NoReturnTool()
    mcp_tool = make_tool(tool)

    assert mcp_tool.name == "no_return"
    assert mcp_tool.description == "This is a test function."
    assert mcp_tool.parameters["properties"]["param"]["description"] == "The parameter."


def test_make_tool_parameter_not_in_docstring() -> None:
    """Test make_tool when a parameter in properties is not in the docstring."""

    class MissingParamTool(BaseMockTool):
        def apply(self, name: str, missing_param: str = "") -> str:
            """This is a test function.

            :param name: The person's name
            """
            return f"Hello {name}! Missing param: {missing_param}"

        def apply_ex(self, *args, **kwargs) -> str:
            return self.apply(**kwargs)

    tool = MissingParamTool()
    mcp_tool = make_tool(tool)

    assert "name" in mcp_tool.parameters["properties"]
    assert "missing_param" in mcp_tool.parameters["properties"]
    assert mcp_tool.parameters["properties"]["name"]["description"] == "The person's name."
    assert "description" not in mcp_tool.parameters["properties"]["missing_param"]


def test_make_tool_multiline_docstring() -> None:
    """Test make_tool with a complex multi-line docstring."""

    class ComplexDocTool(BaseMockTool):
        def apply(self, project_file_path: str, host: str, port: int) -> str:
            """Create an MCP server.

            This function creates and configures a Model Context Protocol server
            with the specified settings.

            :param project_file_path: The path to the project file, or None
            :param host: The host to bind to
            :param port: The port to bind to
            :return: A configured FastMCP server instance
            """
            return f"Server config: {project_file_path}, {host}:{port}"

        def apply_ex(self, *args, **kwargs) -> str:
            return self.apply(**kwargs)

    tool = ComplexDocTool()
    mcp_tool = make_tool(tool)

    assert "Create an MCP server" in mcp_tool.description
    assert "Returns A configured FastMCP server instance" in mcp_tool.description
    assert mcp_tool.parameters["properties"]["project_file_path"]["description"] == "The path to the project file, or None."
    assert mcp_tool.parameters["properties"]["host"]["description"] == "The host to bind to."
    assert mcp_tool.parameters["properties"]["port"]["description"] == "The port to bind to."


def test_make_tool_capitalization_and_periods() -> None:
    """Test that make_tool properly handles capitalization and periods in descriptions."""

    class FormatTool(BaseMockTool):
        def apply(self, param1: str, param2: str, param3: str) -> str:
            """Test function.

            :param param1: lowercase description
            :param param2: description with period.
            :param param3: description with Capitalized word.
            """
            return f"Formatted: {param1}, {param2}, {param3}"

        def apply_ex(self, *args, **kwargs) -> str:
            return self.apply(**kwargs)

    tool = FormatTool()
    mcp_tool = make_tool(tool)

    assert mcp_tool.parameters["properties"]["param1"]["description"] == "Lowercase description."
    assert mcp_tool.parameters["properties"]["param2"]["description"] == "Description with period."
    assert mcp_tool.parameters["properties"]["param3"]["description"] == "Description with Capitalized word."


def test_make_tool_missing_apply() -> None:
    """Test make_tool with a tool that doesn't have an apply method."""

    class BadTool(BaseMockTool):
        pass

    tool = BadTool()

    with pytest.raises(AttributeError):
        make_tool(tool)


@pytest.mark.parametrize(
    "docstring, expected_description",
    [
        (
            """This is a test function.

            :param param: The parameter
            :return: A result
            """,
            "This is a test function. Returns A result.",
        ),
        (
            """
            :param param: The parameter
            :return: A result
            """,
            "Returns A result.",
        ),
        (
            """
            :param param: The parameter
            """,
            "",
        ),
        ("Description without params.", "Description without params."),
    ],
)
def test_make_tool_descriptions(docstring, expected_description) -> None:
    """Test make_tool with various docstring formats."""

    class TestTool(BaseMockTool):
        def apply(self, param: str) -> str:
            return f"Result: {param}"

        def apply_ex(self, *args, **kwargs) -> str:
            return self.apply(**kwargs)

    # Dynamically set the docstring
    TestTool.apply.__doc__ = docstring

    tool = TestTool()
    mcp_tool = make_tool(tool)

    assert mcp_tool.name == "test"
    assert mcp_tool.description == expected_description


def is_test_mock_class(tool_class: type) -> bool:
    """Check if a class is a test mock class."""
    # Check if the class is defined in a test module
    module_name = tool_class.__module__
    return (
        module_name.startswith(("test.", "tests."))
        or "test_" in module_name
        or tool_class.__name__
        in [
            "BaseMockTool",
            "BasicTool",
            "BadTool",
            "NoParamsTool",
            "NoReturnTool",
            "MissingParamTool",
            "ComplexDocTool",
            "FormatTool",
            "NoDescriptionTool",
        ]
    )


@pytest.mark.parametrize("tool_class", ToolRegistry().get_all_tool_classes())
def test_make_tool_all_tools(tool_class) -> None:
    """Test that make_tool works for all tools in the codebase."""
    # Create an instance of the tool
    tool_instance = tool_class(MockAgent())

    # Try to create an MCP tool from it
    mcp_tool = make_tool(tool_instance)

    # Basic validation
    assert isinstance(mcp_tool, MCPTool)
    assert mcp_tool.name == tool_class.get_name_from_cls()

    # The description should be a string (either from docstring or default)
    assert isinstance(mcp_tool.description, str)

```

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

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

import logging
import os
import pathlib
import shutil
import subprocess
import threading

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 InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

from .common import RuntimeDependency, RuntimeDependencyCollection


def run_command(cmd: list, capture_output: bool = True) -> subprocess.CompletedProcess:
    return subprocess.run(
        cmd, stdout=subprocess.PIPE if capture_output else None, stderr=subprocess.STDOUT if capture_output else None, text=True, check=True
    )


def verify_clojure_cli():
    install_msg = "Please install the official Clojure CLI from:\n  https://clojure.org/guides/getting_started"
    if shutil.which("clojure") is None:
        raise FileNotFoundError("`clojure` not found.\n" + install_msg)

    help_proc = run_command(["clojure", "--help"])
    if "-Aaliases" not in help_proc.stdout:
        raise RuntimeError("Detected a Clojure executable, but it does not support '-Aaliases'.\n" + install_msg)

    spath_proc = run_command(["clojure", "-Spath"], capture_output=False)
    if spath_proc.returncode != 0:
        raise RuntimeError("`clojure -Spath` failed; please upgrade to Clojure CLI ≥ 1.10.")


class ClojureLSP(SolidLanguageServer):
    """
    Provides a clojure-lsp specific instantiation of the LanguageServer class. Contains various configurations and settings specific to clojure.
    """

    clojure_lsp_releases = "https://github.com/clojure-lsp/clojure-lsp/releases/latest/download"
    runtime_dependencies = RuntimeDependencyCollection(
        [
            RuntimeDependency(
                id="clojure-lsp",
                url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-aarch64.zip",
                platform_id="osx-arm64",
                archive_type="zip",
                binary_name="clojure-lsp",
            ),
            RuntimeDependency(
                id="clojure-lsp",
                url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-amd64.zip",
                platform_id="osx-x64",
                archive_type="zip",
                binary_name="clojure-lsp",
            ),
            RuntimeDependency(
                id="clojure-lsp",
                url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-aarch64.zip",
                platform_id="linux-arm64",
                archive_type="zip",
                binary_name="clojure-lsp",
            ),
            RuntimeDependency(
                id="clojure-lsp",
                url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-amd64.zip",
                platform_id="linux-x64",
                archive_type="zip",
                binary_name="clojure-lsp",
            ),
            RuntimeDependency(
                id="clojure-lsp",
                url=f"{clojure_lsp_releases}/clojure-lsp-native-windows-amd64.zip",
                platform_id="win-x64",
                archive_type="zip",
                binary_name="clojure-lsp.exe",
            ),
        ]
    )

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        """
        Creates a ClojureLSP instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
        """
        clojure_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
        super().__init__(
            config,
            logger,
            repository_root_path,
            ProcessLaunchInfo(cmd=clojure_lsp_executable_path, cwd=repository_root_path),
            "clojure",
            solidlsp_settings,
        )
        self.server_ready = threading.Event()
        self.initialize_searcher_command_available = threading.Event()
        self.resolve_main_method_available = threading.Event()
        self.service_ready_event = threading.Event()

    @classmethod
    def _setup_runtime_dependencies(
        cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
    ) -> str:
        """Setup runtime dependencies for clojure-lsp and return the command to start the server."""
        verify_clojure_cli()
        deps = ClojureLSP.runtime_dependencies
        dependency = deps.get_single_dep_for_current_platform()

        clojurelsp_ls_dir = cls.ls_resources_dir(solidlsp_settings)
        clojurelsp_executable_path = deps.binary_path(clojurelsp_ls_dir)
        if not os.path.exists(clojurelsp_executable_path):
            logger.log(
                f"Downloading and extracting clojure-lsp from {dependency.url} to {clojurelsp_ls_dir}",
                logging.INFO,
            )
            deps.install(logger, clojurelsp_ls_dir)
        if not os.path.exists(clojurelsp_executable_path):
            raise FileNotFoundError(f"Download failed? Could not find clojure-lsp executable at {clojurelsp_executable_path}")
        os.chmod(clojurelsp_executable_path, 0o755)
        return clojurelsp_executable_path

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """Returns the init params for clojure-lsp."""
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        return {  # type: ignore
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "capabilities": {
                "workspace": {
                    "applyEdit": True,
                    "workspaceEdit": {"documentChanges": True},
                    "symbol": {"symbolKind": {"valueSet": list(range(1, 27))}},
                    "workspaceFolders": True,
                },
                "textDocument": {
                    "synchronization": {"didSave": True},
                    "publishDiagnostics": {"relatedInformation": True, "tagSupport": {"valueSet": [1, 2]}},
                    "definition": {"linkSupport": True},
                    "references": {},
                    "hover": {"contentFormat": ["markdown", "plaintext"]},
                    "documentSymbol": {
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},  #
                    },
                },
                "general": {"positionEncodings": ["utf-16"]},
            },
            "initializationOptions": {"dependency-scheme": "jar", "text-document-sync-kind": "incremental"},
            "trace": "off",
            "workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
        }

    def _start_server(self):
        def register_capability_handler(params):
            assert "registrations" in params
            for registration in params["registrations"]:
                if registration["method"] == "workspace/executeCommand":
                    self.initialize_searcher_command_available.set()
                    self.resolve_main_method_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):
            return []

        def do_nothing(params):
            return

        def check_experimental_status(params):
            if params["quiescent"] == True:
                self.server_ready.set()

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

        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.server.on_notification("experimental/serverStatus", check_experimental_status)

        self.logger.log("Starting clojure-lsp 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" in init_response["capabilities"]
        # Clojure-lsp completion provider capabilities are more flexible than other servers'
        completion_provider = init_response["capabilities"]["completionProvider"]
        assert completion_provider["resolveProvider"] == True
        assert "triggerCharacters" in completion_provider
        self.server.notify.initialized({})
        # after initialize, Clojure-lsp is ready to serve
        self.server_ready.set()
        self.completions_available.set()

```

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

```python
"""
Language Server implementation for TypeScript/JavaScript using https://github.com/yioneko/vtsls,
which provides TypeScript language server functionality via VSCode's TypeScript extension
(contrary to typescript-language-server, which uses the TypeScript compiler directly).
"""

import logging
import os
import pathlib
import shutil
import threading

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 PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

from .common import RuntimeDependency, RuntimeDependencyCollection


class VtsLanguageServer(SolidLanguageServer):
    """
    Provides TypeScript specific instantiation of the LanguageServer class using vtsls.
    Contains various configurations and settings specific to TypeScript via vtsls wrapper.
    """

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        """
        Creates a VtsLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
        """
        vts_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
        super().__init__(
            config,
            logger,
            repository_root_path,
            ProcessLaunchInfo(cmd=vts_lsp_executable_path, cwd=repository_root_path),
            "typescript",
            solidlsp_settings,
        )
        self.server_ready = threading.Event()
        self.initialize_searcher_command_available = threading.Event()

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return super().is_ignored_dirname(dirname) or dirname in [
            "node_modules",
            "dist",
            "build",
            "coverage",
        ]

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

        valid_platforms = [
            PlatformId.LINUX_x64,
            PlatformId.LINUX_arm64,
            PlatformId.OSX,
            PlatformId.OSX_x64,
            PlatformId.OSX_arm64,
            PlatformId.WIN_x64,
            PlatformId.WIN_arm64,
        ]
        assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for vtsls at the moment"

        deps = RuntimeDependencyCollection(
            [
                RuntimeDependency(
                    id="vtsls",
                    description="vtsls language server package",
                    command="npm install --prefix ./ @vtsls/[email protected]",
                    platform_id="any",
                ),
            ]
        )
        vts_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "vts-lsp")
        vts_executable_path = os.path.join(vts_ls_dir, "vtsls")

        # Verify both node and npm are installed
        is_node_installed = shutil.which("node") is not None
        assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
        is_npm_installed = shutil.which("npm") is not None
        assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."

        # Install vtsls if not already installed
        if not os.path.exists(vts_ls_dir):
            os.makedirs(vts_ls_dir, exist_ok=True)
            deps.install(logger, vts_ls_dir)

        vts_executable_path = os.path.join(vts_ls_dir, "node_modules", ".bin", "vtsls")

        assert os.path.exists(vts_executable_path), "vtsls executable not found. Please install @vtsls/language-server and try again."
        return f"{vts_executable_path} --stdio"

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the VTS Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                    "signatureHelp": {"dynamicRegistration": True},
                    "codeAction": {"dynamicRegistration": True},
                },
                "workspace": {
                    "workspaceFolders": True,
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "symbol": {"dynamicRegistration": True},
                    "configuration": True,  # This might be needed for vtsls
                },
            },
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }
        return initialize_params

    def _start_server(self):
        """
        Starts the VTS Language Server, waits for the server to be ready and yields the LanguageServer instance.

        Usage:
        ```
        async with lsp.start_server():
            # LanguageServer has been initialized and ready to serve requests
            await lsp.request_definition(...)
            await lsp.request_references(...)
            # Shutdown the LanguageServer on exit from scope
        # LanguageServer has been shutdown
        """

        def register_capability_handler(params):
            assert "registrations" in params
            for registration in params["registrations"]:
                if registration["method"] == "workspace/executeCommand":
                    self.initialize_searcher_command_available.set()
            return

        def execute_client_command_handler(params):
            return []

        def workspace_configuration_handler(params):
            # VTS may request workspace configuration
            # Return empty configuration for each requested item
            if "items" in params:
                return [{}] * len(params["items"])
            return {}

        def do_nothing(params):
            return

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

        def check_experimental_status(params):
            """
            Also listen for experimental/serverStatus as a backup signal
            """
            if params.get("quiescent") is True:
                self.server_ready.set()
                self.completions_available.set()

        self.server.on_request("client/registerCapability", register_capability_handler)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
        self.server.on_request("workspace/configuration", workspace_configuration_handler)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
        self.server.on_notification("experimental/serverStatus", check_experimental_status)

        self.logger.log("Starting VTS 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)

        # VTS-specific capability checks
        # Be more flexible with capabilities since vtsls might have different structure
        self.logger.log(f"VTS init response capabilities: {init_response['capabilities']}", logging.DEBUG)

        # Basic checks to ensure essential capabilities are present
        assert "textDocumentSync" in init_response["capabilities"]
        assert "completionProvider" in init_response["capabilities"]

        # Log the actual values for debugging
        self.logger.log(f"textDocumentSync: {init_response['capabilities']['textDocumentSync']}", logging.DEBUG)
        self.logger.log(f"completionProvider: {init_response['capabilities']['completionProvider']}", logging.DEBUG)

        self.server.notify.initialized({})
        if self.server_ready.wait(timeout=1.0):
            self.logger.log("VTS server is ready", logging.INFO)
        else:
            self.logger.log("Timeout waiting for VTS server to become ready, proceeding anyway", logging.INFO)
            # Fallback: assume server is ready after timeout
            self.server_ready.set()
        self.completions_available.set()

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

```

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

```python
"""
Provides Bash specific instantiation of the LanguageServer class using bash-language-server.
Contains various configurations and settings specific to Bash scripting.
"""

import logging
import os
import pathlib
import shutil
import threading

from solidlsp import ls_types
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
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 InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings


class BashLanguageServer(SolidLanguageServer):
    """
    Provides Bash specific instantiation of the LanguageServer class using bash-language-server.
    Contains various configurations and settings specific to Bash scripting.
    """

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        """
        Creates a BashLanguageServer instance. This class is not meant to be instantiated directly.
        Use LanguageServer.create() instead.
        """
        bash_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
        super().__init__(
            config,
            logger,
            repository_root_path,
            ProcessLaunchInfo(cmd=bash_lsp_executable_path, cwd=repository_root_path),
            "bash",
            solidlsp_settings,
        )
        self.server_ready = threading.Event()
        self.initialize_searcher_command_available = threading.Event()

    @classmethod
    def _setup_runtime_dependencies(
        cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
    ) -> str:
        """
        Setup runtime dependencies for Bash Language Server and return the command to start the server.
        """
        # Verify both node and npm are installed
        is_node_installed = shutil.which("node") is not None
        assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
        is_npm_installed = shutil.which("npm") is not None
        assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."

        deps = RuntimeDependencyCollection(
            [
                RuntimeDependency(
                    id="bash-language-server",
                    description="bash-language-server package",
                    command="npm install --prefix ./ [email protected]",
                    platform_id="any",
                ),
            ]
        )

        # Install bash-language-server if not already installed
        bash_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "bash-lsp")
        bash_executable_path = os.path.join(bash_ls_dir, "node_modules", ".bin", "bash-language-server")

        # Handle Windows executable extension
        if os.name == "nt":
            bash_executable_path += ".cmd"

        if not os.path.exists(bash_executable_path):
            logger.log(f"Bash Language Server executable not found at {bash_executable_path}. Installing...", logging.INFO)
            deps.install(logger, bash_ls_dir)
            logger.log("Bash language server dependencies installed successfully", logging.INFO)

        if not os.path.exists(bash_executable_path):
            raise FileNotFoundError(
                f"bash-language-server executable not found at {bash_executable_path}, something went wrong with the installation."
            )
        return f"{bash_executable_path} start"

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Bash Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                    "signatureHelp": {"dynamicRegistration": True},
                    "codeAction": {"dynamicRegistration": True},
                },
                "workspace": {
                    "workspaceFolders": True,
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "symbol": {"dynamicRegistration": True},
                },
            },
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }
        return initialize_params

    def _start_server(self):
        """
        Starts the Bash Language Server, waits for the server to be ready and yields the LanguageServer instance.
        """

        def register_capability_handler(params):
            assert "registrations" in params
            for registration in params["registrations"]:
                if registration["method"] == "workspace/executeCommand":
                    self.initialize_searcher_command_available.set()
            return

        def execute_client_command_handler(params):
            return []

        def do_nothing(params):
            return

        def window_log_message(msg):
            self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
            # Check for bash-language-server ready signals
            message_text = msg.get("message", "")
            if "Analyzing" in message_text or "analysis complete" in message_text.lower():
                self.logger.log("Bash language server analysis signals detected", logging.INFO)
                self.server_ready.set()
                self.completions_available.set()

        self.server.on_request("client/registerCapability", register_capability_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.logger.log("Starting Bash 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)
        self.logger.log(f"Received initialize response from bash server: {init_response}", logging.DEBUG)

        # Enhanced capability checks for bash-language-server 5.6.0
        assert init_response["capabilities"]["textDocumentSync"] in [1, 2]  # Full or Incremental
        assert "completionProvider" in init_response["capabilities"]

        # Verify document symbol support is available
        if "documentSymbolProvider" in init_response["capabilities"]:
            self.logger.log("Bash server supports document symbols", logging.INFO)
        else:
            self.logger.log("Warning: Bash server does not report document symbol support", logging.WARNING)

        self.server.notify.initialized({})

        # Wait for server readiness with timeout
        self.logger.log("Waiting for Bash language server to be ready...", logging.INFO)
        if not self.server_ready.wait(timeout=3.0):
            # Fallback: assume server is ready after timeout
            self.logger.log("Timeout waiting for bash server ready signal, proceeding anyway", logging.WARNING)
            self.server_ready.set()
            self.completions_available.set()
        else:
            self.logger.log("Bash server initialization complete", logging.INFO)

    def request_document_symbols(
        self, relative_file_path: str, include_body: bool = False
    ) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]:
        """
        Request document symbols from bash-language-server via LSP.

        Uses the standard LSP documentSymbol request which provides reliable function detection
        for all bash function syntaxes including:
        - function name() { ... } (with function keyword)
        - name() { ... } (traditional syntax)
        - Functions with various indentation levels
        - Functions with comments before/after/inside

        Args:
            relative_file_path: Path to the bash file relative to repository root
            include_body: Whether to include function bodies in symbol information

        Returns:
            Tuple of (all_symbols, root_symbols) detected by the LSP server

        """
        self.logger.log(f"Requesting document symbols via LSP for {relative_file_path}", logging.DEBUG)

        # Use the standard LSP approach - bash-language-server handles all function syntaxes correctly
        all_symbols, root_symbols = super().request_document_symbols(relative_file_path, include_body)

        # Log detection results for debugging
        functions = [s for s in all_symbols if s.get("kind") == 12]
        self.logger.log(
            f"LSP function detection for {relative_file_path}: Found {len(functions)} functions",
            logging.INFO,
        )

        return all_symbols, root_symbols

```

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

```python
"""
Provides Zig specific instantiation of the LanguageServer class using ZLS (Zig Language Server).
"""

import logging
import os
import pathlib
import platform
import shutil
import subprocess
import threading

from overrides import override

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 InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings


class ZigLanguageServer(SolidLanguageServer):
    """
    Provides Zig specific instantiation of the LanguageServer class using ZLS.
    """

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        # For Zig projects, we should ignore:
        # - zig-cache: build cache directory
        # - zig-out: default build output directory
        # - .zig-cache: alternative cache location
        # - node_modules: if the project has JavaScript components
        return super().is_ignored_dirname(dirname) or dirname in ["zig-cache", "zig-out", ".zig-cache", "node_modules", "build", "dist"]

    @staticmethod
    def _get_zig_version():
        """Get the installed Zig version or None if not found."""
        try:
            result = subprocess.run(["zig", "version"], capture_output=True, text=True, check=False)
            if result.returncode == 0:
                return result.stdout.strip()
        except FileNotFoundError:
            return None
        return None

    @staticmethod
    def _get_zls_version():
        """Get the installed ZLS version or None if not found."""
        try:
            result = subprocess.run(["zls", "--version"], capture_output=True, text=True, check=False)
            if result.returncode == 0:
                return result.stdout.strip()
        except FileNotFoundError:
            return None
        return None

    @staticmethod
    def _check_zls_installed():
        """Check if ZLS is installed in the system."""
        return shutil.which("zls") is not None

    @staticmethod
    def _setup_runtime_dependency():
        """
        Check if required Zig runtime dependencies are available.
        Raises RuntimeError with helpful message if dependencies are missing.
        """
        # Check for Windows and provide error message
        if platform.system() == "Windows":
            raise RuntimeError(
                "Windows is not supported by ZLS in this integration. "
                "Cross-file references don't work reliably on Windows. Reason unknown."
            )

        zig_version = ZigLanguageServer._get_zig_version()
        if not zig_version:
            raise RuntimeError(
                "Zig is not installed. Please install Zig from https://ziglang.org/download/ and make sure it is added to your PATH."
            )

        if not ZigLanguageServer._check_zls_installed():
            zls_version = ZigLanguageServer._get_zls_version()
            if not zls_version:
                raise RuntimeError(
                    "Found Zig but ZLS (Zig Language Server) is not installed.\n"
                    "Please install ZLS from https://github.com/zigtools/zls\n"
                    "You can install it via:\n"
                    "  - Package managers (brew install zls, scoop install zls, etc.)\n"
                    "  - Download pre-built binaries from GitHub releases\n"
                    "  - Build from source with: zig build -Doptimize=ReleaseSafe\n\n"
                    "After installation, make sure 'zls' is added to your PATH."
                )

        return True

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        self._setup_runtime_dependency()

        super().__init__(
            config,
            logger,
            repository_root_path,
            ProcessLaunchInfo(cmd="zls", cwd=repository_root_path),
            "zig",
            solidlsp_settings,
        )
        self.server_ready = threading.Event()
        self.request_id = 0

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Zig Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                    "completion": {
                        "dynamicRegistration": True,
                        "completionItem": {
                            "snippetSupport": True,
                            "commitCharactersSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "deprecatedSupport": True,
                            "preselectSupport": True,
                        },
                    },
                    "hover": {
                        "dynamicRegistration": True,
                        "contentFormat": ["markdown", "plaintext"],
                    },
                },
                "workspace": {
                    "workspaceFolders": True,
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "configuration": True,
                },
            },
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
            "initializationOptions": {
                # ZLS specific options based on schema.json
                # Critical paths for ZLS to understand the project
                "zig_exe_path": shutil.which("zig"),  # Path to zig executable
                "zig_lib_path": None,  # Let ZLS auto-detect
                "build_runner_path": None,  # Let ZLS use its built-in runner
                "global_cache_path": None,  # Let ZLS use default cache
                # Build configuration
                "enable_build_on_save": True,  # Enable to analyze project structure
                "build_on_save_args": ["build"],
                # Features
                "enable_snippets": True,
                "enable_argument_placeholders": True,
                "semantic_tokens": "full",
                "warn_style": False,
                "highlight_global_var_declarations": False,
                "skip_std_references": False,
                "prefer_ast_check_as_child_process": True,
                "completion_label_details": True,
                # Inlay hints configuration
                "inlay_hints_show_variable_type_hints": True,
                "inlay_hints_show_struct_literal_field_type": True,
                "inlay_hints_show_parameter_name": True,
                "inlay_hints_show_builtin": True,
                "inlay_hints_exclude_single_argument": True,
                "inlay_hints_hide_redundant_param_names": False,
                "inlay_hints_hide_redundant_param_names_last_token": False,
            },
        }
        return initialize_params

    def _start_server(self):
        """Start ZLS server process"""

        def register_capability_handler(params):
            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("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

        self.logger.log("Starting ZLS 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)

        # Verify server capabilities
        assert "textDocumentSync" in init_response["capabilities"]
        assert "definitionProvider" in init_response["capabilities"]
        assert "documentSymbolProvider" in init_response["capabilities"]
        assert "referencesProvider" in init_response["capabilities"]

        self.server.notify.initialized({})
        self.completions_available.set()

        # ZLS server is ready after initialization
        self.server_ready.set()
        self.server_ready.wait()

        # Open build.zig if it exists to help ZLS understand project structure
        build_zig_path = os.path.join(self.repository_root_path, "build.zig")
        if os.path.exists(build_zig_path):
            try:
                with open(build_zig_path, encoding="utf-8") as f:
                    content = f.read()
                    uri = pathlib.Path(build_zig_path).as_uri()
                    self.server.notify.did_open_text_document(
                        {
                            "textDocument": {
                                "uri": uri,
                                "languageId": "zig",
                                "version": 1,
                                "text": content,
                            }
                        }
                    )
                    self.logger.log("Opened build.zig to provide project context to ZLS", logging.INFO)
            except Exception as e:
                self.logger.log(f"Failed to open build.zig: {e}", logging.WARNING)

```

--------------------------------------------------------------------------------
/test/serena/test_symbol.py:
--------------------------------------------------------------------------------

```python
import pytest

from src.serena.symbol import LanguageServerSymbol


class TestSymbolNameMatching:
    def _create_assertion_error_message(
        self,
        name_path_pattern: str,
        symbol_name_path_parts: list[str],
        is_substring_match: bool,
        expected_result: bool,
        actual_result: bool,
    ) -> str:
        """Helper to create a detailed error message for assertions."""
        qnp_repr = "/".join(symbol_name_path_parts)

        return (
            f"Pattern '{name_path_pattern}' (substring: {is_substring_match}) vs "
            f"Qualname parts {symbol_name_path_parts} (as '{qnp_repr}'). "
            f"Expected: {expected_result}, Got: {actual_result}"
        )

    @pytest.mark.parametrize(
        "name_path_pattern, symbol_name_path_parts, is_substring_match, expected",
        [
            # Exact matches, anywhere in the name (is_substring_match=False)
            pytest.param("foo", ["foo"], False, True, id="'foo' matches 'foo' exactly (simple)"),
            pytest.param("foo/", ["foo"], False, True, id="'foo/' matches 'foo' exactly (simple)"),
            pytest.param("foo", ["bar", "foo"], False, True, id="'foo' matches ['bar', 'foo'] exactly (simple, last element)"),
            pytest.param("foo", ["foobar"], False, False, id="'foo' does not match 'foobar' exactly (simple)"),
            pytest.param(
                "foo", ["bar", "foobar"], False, False, id="'foo' does not match ['bar', 'foobar'] exactly (simple, last element)"
            ),
            pytest.param(
                "foo", ["path", "to", "foo"], False, True, id="'foo' matches ['path', 'to', 'foo'] exactly (simple, last element)"
            ),
            # Exact matches, absolute patterns (is_substring_match=False)
            pytest.param("/foo", ["foo"], False, True, id="'/foo' matches ['foo'] exactly (absolute simple)"),
            pytest.param("/foo", ["foo", "bar"], False, False, id="'/foo' does not match ['foo', 'bar'] (absolute simple, len mismatch)"),
            pytest.param("/foo", ["bar"], False, False, id="'/foo' does not match ['bar'] (absolute simple, name mismatch)"),
            pytest.param(
                "/foo", ["bar", "foo"], False, False, id="'/foo' does not match ['bar', 'foo'] (absolute simple, position mismatch)"
            ),
            # Substring matches, anywhere in the name (is_substring_match=True)
            pytest.param("foo", ["foobar"], True, True, id="'foo' matches 'foobar' as substring (simple)"),
            pytest.param("foo", ["bar", "foobar"], True, True, id="'foo' matches ['bar', 'foobar'] as substring (simple, last element)"),
            pytest.param(
                "foo", ["barfoo"], True, True, id="'foo' matches 'barfoo' as substring (simple)"
            ),  # This was potentially ambiguous before
            pytest.param("foo", ["baz"], True, False, id="'foo' does not match 'baz' as substring (simple)"),
            pytest.param("foo", ["bar", "baz"], True, False, id="'foo' does not match ['bar', 'baz'] as substring (simple, last element)"),
            pytest.param("foo", ["my_foobar_func"], True, True, id="'foo' matches 'my_foobar_func' as substring (simple)"),
            pytest.param(
                "foo",
                ["ClassA", "my_foobar_method"],
                True,
                True,
                id="'foo' matches ['ClassA', 'my_foobar_method'] as substring (simple, last element)",
            ),
            pytest.param("foo", ["my_bar_func"], True, False, id="'foo' does not match 'my_bar_func' as substring (simple)"),
            # Substring matches, absolute patterns (is_substring_match=True)
            pytest.param("/foo", ["foobar"], True, True, id="'/foo' matches ['foobar'] as substring (absolute simple)"),
            pytest.param("/foo/", ["foobar"], True, True, id="'/foo/' matches ['foobar'] as substring (absolute simple, last element)"),
            pytest.param("/foo", ["barfoobaz"], True, True, id="'/foo' matches ['barfoobaz'] as substring (absolute simple)"),
            pytest.param(
                "/foo", ["foo", "bar"], True, False, id="'/foo' does not match ['foo', 'bar'] as substring (absolute simple, len mismatch)"
            ),
            pytest.param("/foo", ["bar"], True, False, id="'/foo' does not match ['bar'] (absolute simple, no substr)"),
            pytest.param(
                "/foo", ["bar", "foo"], True, False, id="'/foo' does not match ['bar', 'foo'] (absolute simple, position mismatch)"
            ),
            pytest.param(
                "/foo/", ["bar", "foo"], True, False, id="'/foo/' does not match ['bar', 'foo'] (absolute simple, position mismatch)"
            ),
        ],
    )
    def test_match_simple_name(self, name_path_pattern, symbol_name_path_parts, is_substring_match, expected):
        """Tests matching for simple names (no '/' in pattern)."""
        result = LanguageServerSymbol.match_name_path(name_path_pattern, symbol_name_path_parts, is_substring_match)
        error_msg = self._create_assertion_error_message(name_path_pattern, symbol_name_path_parts, is_substring_match, expected, result)
        assert result == expected, error_msg

    @pytest.mark.parametrize(
        "name_path_pattern, symbol_name_path_parts, is_substring_match, expected",
        [
            # --- Relative patterns (suffix matching) ---
            # Exact matches, relative patterns (is_substring_match=False)
            pytest.param("bar/foo", ["bar", "foo"], False, True, id="R: 'bar/foo' matches ['bar', 'foo'] exactly"),
            pytest.param("bar/foo", ["mod", "bar", "foo"], False, True, id="R: 'bar/foo' matches ['mod', 'bar', 'foo'] exactly (suffix)"),
            pytest.param(
                "bar/foo", ["bar", "foo", "baz"], False, False, id="R: 'bar/foo' does not match ['bar', 'foo', 'baz'] (pattern shorter)"
            ),
            pytest.param("bar/foo", ["bar"], False, False, id="R: 'bar/foo' does not match ['bar'] (pattern longer)"),
            pytest.param("bar/foo", ["baz", "foo"], False, False, id="R: 'bar/foo' does not match ['baz', 'foo'] (first part mismatch)"),
            pytest.param("bar/foo", ["bar", "baz"], False, False, id="R: 'bar/foo' does not match ['bar', 'baz'] (last part mismatch)"),
            pytest.param("bar/foo", ["foo"], False, False, id="R: 'bar/foo' does not match ['foo'] (pattern longer)"),
            pytest.param(
                "bar/foo", ["other", "foo"], False, False, id="R: 'bar/foo' does not match ['other', 'foo'] (first part mismatch)"
            ),
            pytest.param(
                "bar/foo", ["bar", "otherfoo"], False, False, id="R: 'bar/foo' does not match ['bar', 'otherfoo'] (last part mismatch)"
            ),
            # Substring matches, relative patterns (is_substring_match=True)
            pytest.param("bar/foo", ["bar", "foobar"], True, True, id="R: 'bar/foo' matches ['bar', 'foobar'] as substring"),
            pytest.param(
                "bar/foo", ["mod", "bar", "foobar"], True, True, id="R: 'bar/foo' matches ['mod', 'bar', 'foobar'] as substring (suffix)"
            ),
            pytest.param("bar/foo", ["bar", "bazfoo"], True, True, id="R: 'bar/foo' matches ['bar', 'bazfoo'] as substring"),
            pytest.param("bar/fo", ["bar", "foo"], True, True, id="R: 'bar/fo' matches ['bar', 'foo'] as substring"),  # codespell:ignore
            pytest.param("bar/foo", ["bar", "baz"], True, False, id="R: 'bar/foo' does not match ['bar', 'baz'] (last no substr)"),
            pytest.param(
                "bar/foo", ["baz", "foobar"], True, False, id="R: 'bar/foo' does not match ['baz', 'foobar'] (first part mismatch)"
            ),
            pytest.param(
                "bar/foo", ["bar", "my_foobar_method"], True, True, id="R: 'bar/foo' matches ['bar', 'my_foobar_method'] as substring"
            ),
            pytest.param(
                "bar/foo",
                ["mod", "bar", "my_foobar_method"],
                True,
                True,
                id="R: 'bar/foo' matches ['mod', 'bar', 'my_foobar_method'] as substring (suffix)",
            ),
            pytest.param(
                "bar/foo",
                ["bar", "another_method"],
                True,
                False,
                id="R: 'bar/foo' does not match ['bar', 'another_method'] (last no substr)",
            ),
            pytest.param(
                "bar/foo",
                ["other", "my_foobar_method"],
                True,
                False,
                id="R: 'bar/foo' does not match ['other', 'my_foobar_method'] (first part mismatch)",
            ),
            pytest.param("bar/f", ["bar", "foo"], True, True, id="R: 'bar/f' matches ['bar', 'foo'] as substring"),
            # Exact matches, absolute patterns (is_substring_match=False)
            pytest.param("/bar/foo", ["bar", "foo"], False, True, id="A: '/bar/foo' matches ['bar', 'foo'] exactly"),
            pytest.param(
                "/bar/foo", ["bar", "foo", "baz"], False, False, id="A: '/bar/foo' does not match ['bar', 'foo', 'baz'] (pattern shorter)"
            ),
            pytest.param("/bar/foo", ["bar"], False, False, id="A: '/bar/foo' does not match ['bar'] (pattern longer)"),
            pytest.param("/bar/foo", ["baz", "foo"], False, False, id="A: '/bar/foo' does not match ['baz', 'foo'] (first part mismatch)"),
            pytest.param("/bar/foo", ["bar", "baz"], False, False, id="A: '/bar/foo' does not match ['bar', 'baz'] (last part mismatch)"),
            # Substring matches (is_substring_match=True)
            pytest.param("/bar/foo", ["bar", "foobar"], True, True, id="A: '/bar/foo' matches ['bar', 'foobar'] as substring"),
            pytest.param("/bar/foo", ["bar", "bazfoo"], True, True, id="A: '/bar/foo' matches ['bar', 'bazfoo'] as substring"),
            pytest.param("/bar/fo", ["bar", "foo"], True, True, id="A: '/bar/fo' matches ['bar', 'foo'] as substring"),  # codespell:ignore
            pytest.param("/bar/foo", ["bar", "baz"], True, False, id="A: '/bar/foo' does not match ['bar', 'baz'] (last no substr)"),
            pytest.param(
                "/bar/foo", ["baz", "foobar"], True, False, id="A: '/bar/foo' does not match ['baz', 'foobar'] (first part mismatch)"
            ),
        ],
    )
    def test_match_name_path_pattern_path_len_2(self, name_path_pattern, symbol_name_path_parts, is_substring_match, expected):
        """Tests matching for qualified names (e.g. 'module/class/func')."""
        result = LanguageServerSymbol.match_name_path(name_path_pattern, symbol_name_path_parts, is_substring_match)
        error_msg = self._create_assertion_error_message(name_path_pattern, symbol_name_path_parts, is_substring_match, expected, result)
        assert result == expected, error_msg

```

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

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

import logging
import os
import pathlib
import shutil
import threading

from overrides import override
from sensai.util.logging import LogTime

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

from .common import RuntimeDependency, RuntimeDependencyCollection

# Platform-specific imports
if os.name != "nt":  # Unix-like systems
    import pwd
else:
    # Dummy pwd module for Windows
    class pwd:
        @staticmethod
        def getpwuid(uid):
            return type("obj", (), {"pw_name": os.environ.get("USERNAME", "unknown")})()


# Conditionally import pwd module (Unix-only)
if not PlatformUtils.get_platform_id().value.startswith("win"):
    pass


class TypeScriptLanguageServer(SolidLanguageServer):
    """
    Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript.
    """

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        """
        Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
        """
        ts_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
        super().__init__(
            config,
            logger,
            repository_root_path,
            ProcessLaunchInfo(cmd=ts_lsp_executable_path, cwd=repository_root_path),
            "typescript",
            solidlsp_settings,
        )
        self.server_ready = threading.Event()
        self.initialize_searcher_command_available = threading.Event()

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return super().is_ignored_dirname(dirname) or dirname in [
            "node_modules",
            "dist",
            "build",
            "coverage",
        ]

    @classmethod
    def _setup_runtime_dependencies(
        cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
    ) -> list[str]:
        """
        Setup runtime dependencies for TypeScript Language Server and return the command to start the server.
        """
        platform_id = PlatformUtils.get_platform_id()

        valid_platforms = [
            PlatformId.LINUX_x64,
            PlatformId.LINUX_arm64,
            PlatformId.OSX,
            PlatformId.OSX_x64,
            PlatformId.OSX_arm64,
            PlatformId.WIN_x64,
            PlatformId.WIN_arm64,
        ]
        assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment"

        deps = RuntimeDependencyCollection(
            [
                RuntimeDependency(
                    id="typescript",
                    description="typescript package",
                    command=["npm", "install", "--prefix", "./", "[email protected]"],
                    platform_id="any",
                ),
                RuntimeDependency(
                    id="typescript-language-server",
                    description="typescript-language-server package",
                    command=["npm", "install", "--prefix", "./", "[email protected]"],
                    platform_id="any",
                ),
            ]
        )

        # Verify both node and npm are installed
        is_node_installed = shutil.which("node") is not None
        assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
        is_npm_installed = shutil.which("npm") is not None
        assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."

        # Verify both node and npm are installed
        is_node_installed = shutil.which("node") is not None
        assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
        is_npm_installed = shutil.which("npm") is not None
        assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."

        # Install typescript and typescript-language-server if not already installed
        tsserver_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "ts-lsp")
        tsserver_executable_path = os.path.join(tsserver_ls_dir, "node_modules", ".bin", "typescript-language-server")
        if not os.path.exists(tsserver_executable_path):
            logger.log(f"Typescript Language Server executable not found at {tsserver_executable_path}. Installing...", logging.INFO)
            with LogTime("Installation of TypeScript language server dependencies", logger=logger.logger):
                deps.install(logger, tsserver_ls_dir)

        if not os.path.exists(tsserver_executable_path):
            raise FileNotFoundError(
                f"typescript-language-server executable not found at {tsserver_executable_path}, something went wrong with the installation."
            )
        return [tsserver_executable_path, "--stdio"]

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the TypeScript Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                    "signatureHelp": {"dynamicRegistration": True},
                    "codeAction": {"dynamicRegistration": True},
                },
                "workspace": {
                    "workspaceFolders": True,
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "symbol": {"dynamicRegistration": True},
                },
            },
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }
        return initialize_params

    def _start_server(self):
        """
        Starts the TypeScript Language Server, waits for the server to be ready and yields the LanguageServer instance.

        Usage:
        ```
        async with lsp.start_server():
            # LanguageServer has been initialized and ready to serve requests
            await lsp.request_definition(...)
            await lsp.request_references(...)
            # Shutdown the LanguageServer on exit from scope
        # LanguageServer has been shutdown
        """

        def register_capability_handler(params):
            assert "registrations" in params
            for registration in params["registrations"]:
                if registration["method"] == "workspace/executeCommand":
                    self.initialize_searcher_command_available.set()
                    # TypeScript doesn't have a direct equivalent to resolve_main_method
                    # You might want to set a different flag or remove this line
                    # self.resolve_main_method_available.set()
            return

        def execute_client_command_handler(params):
            return []

        def do_nothing(params):
            return

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

        def check_experimental_status(params):
            """
            Also listen for experimental/serverStatus as a backup signal
            """
            if params.get("quiescent") == True:
                self.server_ready.set()
                self.completions_available.set()

        self.server.on_request("client/registerCapability", register_capability_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("experimental/serverStatus", check_experimental_status)

        self.logger.log("Starting TypeScript 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)

        # TypeScript-specific capability checks
        assert init_response["capabilities"]["textDocumentSync"] == 2
        assert "completionProvider" in init_response["capabilities"]
        assert init_response["capabilities"]["completionProvider"] == {
            "triggerCharacters": [".", '"', "'", "/", "@", "<"],
            "resolveProvider": True,
        }

        self.server.notify.initialized({})
        if self.server_ready.wait(timeout=1.0):
            self.logger.log("TypeScript server is ready", logging.INFO)
        else:
            self.logger.log("Timeout waiting for TypeScript server to become ready, proceeding anyway", logging.INFO)
            # Fallback: assume server is ready after timeout
            self.server_ready.set()
        self.completions_available.set()

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

```

--------------------------------------------------------------------------------
/test/solidlsp/nix/test_nix_basic.py:
--------------------------------------------------------------------------------

```python
"""
Tests for the Nix language server implementation using nixd.

These tests validate symbol finding and cross-file reference capabilities for Nix expressions.
"""

import platform

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language

# Skip all Nix tests on Windows as Nix doesn't support Windows
pytestmark = pytest.mark.skipif(platform.system() == "Windows", reason="Nix and nil are not available on Windows")


@pytest.mark.nix
class TestNixLanguageServer:
    """Test Nix language server symbol finding capabilities."""

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_find_symbols_in_default_nix(self, language_server: SolidLanguageServer) -> None:
        """Test finding specific symbols in default.nix."""
        symbols = language_server.request_document_symbols("default.nix")

        assert symbols is not None
        assert len(symbols) > 0

        # Extract symbol names from the returned structure
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        symbol_names = {sym.get("name") for sym in symbol_list if isinstance(sym, dict)}

        # Verify specific function exists
        assert "makeGreeting" in symbol_names, "makeGreeting function not found"

        # Verify exact attribute sets are found
        expected_attrs = {"listUtils", "stringUtils"}
        found_attrs = symbol_names & expected_attrs
        assert found_attrs == expected_attrs, f"Expected exactly {expected_attrs}, found {found_attrs}"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_find_symbols_in_utils(self, language_server: SolidLanguageServer) -> None:
        """Test finding symbols in lib/utils.nix."""
        symbols = language_server.request_document_symbols("lib/utils.nix")

        assert symbols is not None
        assert len(symbols) > 0

        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        symbol_names = {sym.get("name") for sym in symbol_list if isinstance(sym, dict)}

        # Verify exact utility modules are found
        expected_modules = {"math", "strings", "lists", "attrs"}
        found_modules = symbol_names & expected_modules
        assert found_modules == expected_modules, f"Expected exactly {expected_modules}, found {found_modules}"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_find_symbols_in_flake(self, language_server: SolidLanguageServer) -> None:
        """Test finding symbols in flake.nix."""
        symbols = language_server.request_document_symbols("flake.nix")

        assert symbols is not None
        assert len(symbols) > 0

        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        symbol_names = {sym.get("name") for sym in symbol_list if isinstance(sym, dict)}

        # Flakes must have either inputs or outputs
        assert "inputs" in symbol_names or "outputs" in symbol_names, "Flake must have inputs or outputs"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_find_symbols_in_module(self, language_server: SolidLanguageServer) -> None:
        """Test finding symbols in a NixOS module."""
        symbols = language_server.request_document_symbols("modules/example.nix")

        assert symbols is not None
        assert len(symbols) > 0

        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        symbol_names = {sym.get("name") for sym in symbol_list if isinstance(sym, dict)}

        # NixOS modules must have either options or config
        assert "options" in symbol_names or "config" in symbol_names, "Module must have options or config"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
        """Test finding references within the same file."""
        symbols = language_server.request_document_symbols("default.nix")

        assert symbols is not None
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols

        # Find makeGreeting function
        greeting_symbol = None
        for sym in symbol_list:
            if sym.get("name") == "makeGreeting":
                greeting_symbol = sym
                break

        assert greeting_symbol is not None, "makeGreeting function not found"
        assert "range" in greeting_symbol, "Symbol must have range information"

        range_start = greeting_symbol["range"]["start"]
        refs = language_server.request_references("default.nix", range_start["line"], range_start["character"])

        assert refs is not None
        assert isinstance(refs, list)
        # nixd finds at least the inherit statement (line 67)
        assert len(refs) >= 1, f"Should find at least 1 reference to makeGreeting, found {len(refs)}"

        # Verify makeGreeting is referenced at expected locations
        if refs:
            ref_lines = sorted([ref["range"]["start"]["line"] for ref in refs])
            # Check if we found the inherit (line 67, 0-indexed: 66)
            assert 66 in ref_lines, f"Should find makeGreeting inherit at line 67, found at lines {[l+1 for l in ref_lines]}"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_hover_information(self, language_server: SolidLanguageServer) -> None:
        """Test hover information for symbols."""
        # Get hover info for makeGreeting function
        hover_info = language_server.request_hover("default.nix", 9, 5)  # Position near makeGreeting

        assert hover_info is not None, "Should provide hover information"

        if isinstance(hover_info, dict) and len(hover_info) > 0:
            # If hover info is provided, it should have proper structure
            assert "contents" in hover_info or "value" in hover_info, "Hover should have contents or value"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_cross_file_references_utils_import(self, language_server: SolidLanguageServer) -> None:
        """Test finding cross-file references for imported utils."""
        # Find references to 'utils' which is imported in default.nix from lib/utils.nix
        # Line 10 in default.nix: utils = import ./lib/utils.nix { inherit lib; };
        refs = language_server.request_references("default.nix", 9, 2)  # Position of 'utils'

        assert refs is not None
        assert isinstance(refs, list)

        # Should find references within default.nix where utils is used
        default_refs = [ref for ref in refs if "default.nix" in ref.get("uri", "")]
        # utils is: imported (line 10), used in listUtils.unique (line 24), inherited in exports (line 69)
        assert len(default_refs) >= 2, f"Should find at least 2 references to utils in default.nix, found {len(default_refs)}"

        # Verify utils is referenced at expected locations (0-indexed)
        if default_refs:
            ref_lines = sorted([ref["range"]["start"]["line"] for ref in default_refs])
            # Check for key references - at least the import (line 10) or usage (line 24)
            assert (
                9 in ref_lines or 23 in ref_lines
            ), f"Should find utils import or usage, found references at lines {[l+1 for l in ref_lines]}"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_verify_imports_exist(self, language_server: SolidLanguageServer) -> None:
        """Verify that our test files have proper imports set up."""
        # Verify that default.nix imports utils from lib/utils.nix
        symbols = language_server.request_document_symbols("default.nix")

        assert symbols is not None
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols

        # Check that makeGreeting exists (defined in default.nix)
        symbol_names = {sym.get("name") for sym in symbol_list if isinstance(sym, dict)}
        assert "makeGreeting" in symbol_names, "makeGreeting should be found in default.nix"

        # Verify lib/utils.nix has the expected structure
        utils_symbols = language_server.request_document_symbols("lib/utils.nix")
        assert utils_symbols is not None
        utils_list = utils_symbols[0] if isinstance(utils_symbols, tuple) else utils_symbols
        utils_names = {sym.get("name") for sym in utils_list if isinstance(sym, dict)}

        # Verify key functions exist in utils
        assert "math" in utils_names, "math should be found in lib/utils.nix"
        assert "strings" in utils_names, "strings should be found in lib/utils.nix"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_go_to_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
        """Test go-to-definition from default.nix to lib/utils.nix."""
        # Line 24 in default.nix: unique = utils.lists.unique;
        # Test go-to-definition for 'utils'
        definitions = language_server.request_definition("default.nix", 23, 14)  # Position of 'utils'

        assert definitions is not None
        assert isinstance(definitions, list)

        if len(definitions) > 0:
            # Should point to the import statement or utils.nix
            assert any(
                "utils" in def_item.get("uri", "") or "default.nix" in def_item.get("uri", "") for def_item in definitions
            ), "Definition should relate to utils import or utils.nix file"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_definition_navigation_in_flake(self, language_server: SolidLanguageServer) -> None:
        """Test definition navigation in flake.nix."""
        # Test that we can navigate to definitions within flake.nix
        # Line 69: default = hello-custom;
        definitions = language_server.request_definition("flake.nix", 68, 20)  # Position of 'hello-custom'

        assert definitions is not None
        assert isinstance(definitions, list)
        # nixd should find the definition of hello-custom in the same file
        if len(definitions) > 0:
            assert any(
                "flake.nix" in def_item.get("uri", "") for def_item in definitions
            ), "Should find hello-custom definition in flake.nix"

    @pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
    def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
        """Test that full symbol tree is not empty."""
        symbols = language_server.request_full_symbol_tree()

        assert symbols is not None
        assert len(symbols) > 0, "Symbol tree should not be empty"

        # The tree should have at least one root node
        root = symbols[0]
        assert isinstance(root, dict), "Root should be a dict"
        assert "name" in root, "Root should have a name"

```

--------------------------------------------------------------------------------
/test/serena/test_serena_agent.py:
--------------------------------------------------------------------------------

```python
import json
import logging
import os
import time

import pytest

import test.solidlsp.clojure as clj
from serena.agent import SerenaAgent
from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig
from serena.project import Project
from serena.tools import FindReferencingSymbolsTool, FindSymbolTool
from solidlsp.ls_config import Language
from test.conftest import get_repo_path


@pytest.fixture
def serena_config():
    """Create an in-memory configuration for tests with test repositories pre-registered."""
    # Create test projects for all supported languages
    test_projects = []
    for language in [
        Language.PYTHON,
        Language.GO,
        Language.JAVA,
        Language.KOTLIN,
        Language.RUST,
        Language.TYPESCRIPT,
        Language.PHP,
        Language.CSHARP,
        Language.CLOJURE,
    ]:
        repo_path = get_repo_path(language)
        if repo_path.exists():
            project_name = f"test_repo_{language}"
            project = Project(
                project_root=str(repo_path),
                project_config=ProjectConfig(
                    project_name=project_name,
                    language=language,
                    ignored_paths=[],
                    excluded_tools=set(),
                    read_only=False,
                    ignore_all_files_in_gitignore=True,
                    initial_prompt="",
                    encoding="utf-8",
                ),
            )
            test_projects.append(RegisteredProject.from_project_instance(project))

    config = SerenaConfig(gui_log_window_enabled=False, web_dashboard=False, log_level=logging.ERROR)
    config.projects = test_projects
    return config


@pytest.fixture
def serena_agent(request: pytest.FixtureRequest, serena_config):
    language = Language(request.param)
    project_name = f"test_repo_{language}"

    return SerenaAgent(project=project_name, serena_config=serena_config)


class TestSerenaAgent:
    @pytest.mark.parametrize(
        "serena_agent,symbol_name,expected_kind,expected_file",
        [
            pytest.param(Language.PYTHON, "User", "Class", "models.py", marks=pytest.mark.python),
            pytest.param(Language.GO, "Helper", "Function", "main.go", marks=pytest.mark.go),
            pytest.param(Language.JAVA, "Model", "Class", "Model.java", marks=pytest.mark.java),
            pytest.param(Language.KOTLIN, "Model", "Struct", "Model.kt", marks=pytest.mark.kotlin),
            pytest.param(Language.RUST, "add", "Function", "lib.rs", marks=pytest.mark.rust),
            pytest.param(Language.TYPESCRIPT, "DemoClass", "Class", "index.ts", marks=pytest.mark.typescript),
            pytest.param(Language.PHP, "helperFunction", "Function", "helper.php", marks=pytest.mark.php),
            pytest.param(
                Language.CLOJURE,
                "greet",
                "Function",
                clj.CORE_PATH,
                marks=[pytest.mark.clojure, pytest.mark.skipif(clj.CLI_FAIL, reason=f"Clojure CLI not available: {clj.CLI_FAIL}")],
            ),
            pytest.param(Language.CSHARP, "Calculator", "Class", "Program.cs", marks=pytest.mark.csharp),
        ],
        indirect=["serena_agent"],
    )
    def test_find_symbol(self, serena_agent, symbol_name: str, expected_kind: str, expected_file: str):
        agent = serena_agent
        find_symbol_tool = agent.get_tool(FindSymbolTool)
        result = find_symbol_tool.apply_ex(name_path=symbol_name)

        symbols = json.loads(result)
        assert any(
            symbol_name in s["name_path"] and expected_kind.lower() in s["kind"].lower() and expected_file in s["relative_path"]
            for s in symbols
        ), f"Expected to find {symbol_name} ({expected_kind}) in {expected_file}"

    @pytest.mark.parametrize(
        "serena_agent,symbol_name,def_file,ref_file",
        [
            pytest.param(
                Language.PYTHON,
                "User",
                os.path.join("test_repo", "models.py"),
                os.path.join("test_repo", "services.py"),
                marks=pytest.mark.python,
            ),
            pytest.param(Language.GO, "Helper", "main.go", "main.go", marks=pytest.mark.go),
            pytest.param(
                Language.JAVA,
                "Model",
                os.path.join("src", "main", "java", "test_repo", "Model.java"),
                os.path.join("src", "main", "java", "test_repo", "Main.java"),
                marks=pytest.mark.java,
            ),
            pytest.param(
                Language.KOTLIN,
                "Model",
                os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"),
                os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"),
                marks=pytest.mark.kotlin,
            ),
            pytest.param(Language.RUST, "add", os.path.join("src", "lib.rs"), os.path.join("src", "main.rs"), marks=pytest.mark.rust),
            pytest.param(Language.TYPESCRIPT, "helperFunction", "index.ts", "use_helper.ts", marks=pytest.mark.typescript),
            pytest.param(Language.PHP, "helperFunction", "helper.php", "index.php", marks=pytest.mark.php),
            pytest.param(
                Language.CLOJURE,
                "multiply",
                clj.CORE_PATH,
                clj.UTILS_PATH,
                marks=[pytest.mark.clojure, pytest.mark.skipif(clj.CLI_FAIL, reason=f"Clojure CLI not available: {clj.CLI_FAIL}")],
            ),
            pytest.param(Language.CSHARP, "Calculator", "Program.cs", "Program.cs", marks=pytest.mark.csharp),
        ],
        indirect=["serena_agent"],
    )
    def test_find_symbol_references(self, serena_agent, symbol_name: str, def_file: str, ref_file: str) -> None:
        agent = serena_agent

        # Find the symbol location first
        find_symbol_tool = agent.get_tool(FindSymbolTool)
        result = find_symbol_tool.apply_ex(name_path=symbol_name, relative_path=def_file)

        time.sleep(1)
        symbols = json.loads(result)
        # Find the definition
        def_symbol = symbols[0]

        # Now find references
        find_refs_tool = agent.get_tool(FindReferencingSymbolsTool)
        result = find_refs_tool.apply_ex(name_path=def_symbol["name_path"], relative_path=def_symbol["relative_path"])

        refs = json.loads(result)
        assert any(
            ref["relative_path"] == ref_file for ref in refs
        ), f"Expected to find reference to {symbol_name} in {ref_file}. refs={refs}"

    @pytest.mark.parametrize(
        "serena_agent,name_path,substring_matching,expected_symbol_name,expected_kind,expected_file",
        [
            pytest.param(
                Language.PYTHON,
                "OuterClass/NestedClass",
                False,
                "NestedClass",
                "Class",
                os.path.join("test_repo", "nested.py"),
                id="exact_qualname_class",
                marks=pytest.mark.python,
            ),
            pytest.param(
                Language.PYTHON,
                "OuterClass/NestedClass/find_me",
                False,
                "find_me",
                "Method",
                os.path.join("test_repo", "nested.py"),
                id="exact_qualname_method",
                marks=pytest.mark.python,
            ),
            pytest.param(
                Language.PYTHON,
                "OuterClass/NestedCl",  # Substring for NestedClass
                True,
                "NestedClass",
                "Class",
                os.path.join("test_repo", "nested.py"),
                id="substring_qualname_class",
                marks=pytest.mark.python,
            ),
            pytest.param(
                Language.PYTHON,
                "OuterClass/NestedClass/find_m",  # Substring for find_me
                True,
                "find_me",
                "Method",
                os.path.join("test_repo", "nested.py"),
                id="substring_qualname_method",
                marks=pytest.mark.python,
            ),
            pytest.param(
                Language.PYTHON,
                "/OuterClass",  # Absolute path
                False,
                "OuterClass",
                "Class",
                os.path.join("test_repo", "nested.py"),
                id="absolute_qualname_class",
                marks=pytest.mark.python,
            ),
            pytest.param(
                Language.PYTHON,
                "/OuterClass/NestedClass/find_m",  # Absolute path with substring
                True,
                "find_me",
                "Method",
                os.path.join("test_repo", "nested.py"),
                id="absolute_substring_qualname_method",
                marks=pytest.mark.python,
            ),
        ],
        indirect=["serena_agent"],
    )
    def test_find_symbol_name_path(
        self,
        serena_agent,
        name_path: str,
        substring_matching: bool,
        expected_symbol_name: str,
        expected_kind: str,
        expected_file: str,
    ):
        agent = serena_agent

        find_symbol_tool = agent.get_tool(FindSymbolTool)
        result = find_symbol_tool.apply_ex(
            name_path=name_path,
            depth=0,
            relative_path=None,
            include_body=False,
            include_kinds=None,
            exclude_kinds=None,
            substring_matching=substring_matching,
        )

        symbols = json.loads(result)
        assert any(
            expected_symbol_name == s["name_path"].split("/")[-1]
            and expected_kind.lower() in s["kind"].lower()
            and expected_file in s["relative_path"]
            for s in symbols
        ), f"Expected to find {name_path} ({expected_kind}) in {expected_file} for {agent._active_project.language.name}. Symbols: {symbols}"

    @pytest.mark.parametrize(
        "serena_agent,name_path",
        [
            pytest.param(
                Language.PYTHON,
                "/NestedClass",  # Absolute path, NestedClass is not top-level
                id="absolute_path_non_top_level_no_match",
                marks=pytest.mark.python,
            ),
            pytest.param(
                Language.PYTHON,
                "/NoSuchParent/NestedClass",  # Absolute path with non-existent parent
                id="absolute_path_non_existent_parent_no_match",
                marks=pytest.mark.python,
            ),
        ],
        indirect=["serena_agent"],
    )
    def test_find_symbol_name_path_no_match(
        self,
        serena_agent,
        name_path: str,
    ):
        agent = serena_agent

        find_symbol_tool = agent.get_tool(FindSymbolTool)
        result = find_symbol_tool.apply_ex(
            name_path=name_path,
            depth=0,
            substring_matching=True,
        )

        symbols = json.loads(result)
        assert not symbols, f"Expected to find no symbols for {name_path}. Symbols found: {symbols}"

```

--------------------------------------------------------------------------------
/test/solidlsp/clojure/test_clojure_basic.py:
--------------------------------------------------------------------------------

```python
import pytest

from serena.project import Project
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import UnifiedSymbolInformation

from . import CLI_FAIL, CORE_PATH, UTILS_PATH


@pytest.mark.clojure
@pytest.mark.skipif(CLI_FAIL, reason=f"Clojure CLI not available: {CLI_FAIL}")
class TestLanguageServerBasics:
    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_basic_definition(self, language_server: SolidLanguageServer):
        """
        Test finding definition of 'greet' function call in core.clj
        """
        result = language_server.request_definition(CORE_PATH, 20, 12)  # Position of 'greet' in (greet "World")

        assert isinstance(result, list)
        assert len(result) >= 1

        definition = result[0]
        assert definition["relativePath"] == CORE_PATH
        assert definition["range"]["start"]["line"] == 2, "Should find the definition of greet function at line 2"

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_cross_file_references(self, language_server: SolidLanguageServer):
        """
        Test finding references to 'multiply' function from core.clj
        """
        result = language_server.request_references(CORE_PATH, 12, 6)

        assert isinstance(result, list) and len(result) >= 2, "Should find definition + usage in utils.clj"

        usage_found = any(
            item["relativePath"] == UTILS_PATH and item["range"]["start"]["line"] == 6  # multiply usage in calculate-area
            for item in result
        )
        assert usage_found, "Should find multiply usage in utils.clj"

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_completions(self, language_server: SolidLanguageServer):
        with language_server.open_file(UTILS_PATH):
            # After "core/" in calculate-area
            result = language_server.request_completions(UTILS_PATH, 6, 8)

            assert isinstance(result, list) and len(result) > 0

            completion_texts = [item["completionText"] for item in result]
            assert any("multiply" in text for text in completion_texts), "Should find 'multiply' function in completions after 'core/'"

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_document_symbols(self, language_server: SolidLanguageServer):
        symbols, _ = language_server.request_document_symbols(CORE_PATH)

        assert isinstance(symbols, list) and len(symbols) >= 4, "greet, add, multiply, -main functions"

        # Check that we find the expected function symbols
        symbol_names = [symbol["name"] for symbol in symbols]
        expected_functions = ["greet", "add", "multiply", "-main"]

        for func_name in expected_functions:
            assert func_name in symbol_names, f"Should find {func_name} function in symbols"

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_hover(self, language_server: SolidLanguageServer):
        """Test hover on greet function"""
        result = language_server.request_hover(CORE_PATH, 2, 7)

        assert result is not None, "Hover should return information for greet function"
        assert "contents" in result
        # Should contain function signature or documentation
        contents = result["contents"]
        if isinstance(contents, str):
            assert "greet" in contents.lower()
        elif isinstance(contents, dict) and "value" in contents:
            assert "greet" in contents["value"].lower()
        else:
            assert False, f"Unexpected contents format: {type(contents)}"

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_workspace_symbols(self, language_server: SolidLanguageServer):
        # Search for functions containing "add"
        result = language_server.request_workspace_symbol("add")

        assert isinstance(result, list) and len(result) > 0, "Should find at least one symbol containing 'add'"

        # Should find the 'add' function
        symbol_names = [symbol["name"] for symbol in result]
        assert any("add" in name.lower() for name in symbol_names), f"Should find 'add' function in symbols: {symbol_names}"

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_namespace_functions(self, language_server: SolidLanguageServer):
        """Test definition lookup for core/greet usage in utils.clj"""
        # Position of 'greet' in core/greet call
        result = language_server.request_definition(UTILS_PATH, 11, 25)

        assert isinstance(result, list)
        assert len(result) >= 1

        definition = result[0]
        assert definition["relativePath"] == CORE_PATH, "Should find the definition of greet in core.clj"

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_request_references_with_content(self, language_server: SolidLanguageServer):
        """Test references to multiply function with content"""
        references = language_server.request_references(CORE_PATH, 12, 6)
        result = [
            language_server.retrieve_content_around_line(ref1["relativePath"], ref1["range"]["start"]["line"], 3, 0) for ref1 in references
        ]

        assert result is not None, "Should find references with content"
        assert isinstance(result, list)
        assert len(result) >= 2, "Should find definition + usage in utils.clj"

        for ref in result:
            assert ref.source_file_path is not None, "Each reference should have a source file path"
            content_str = ref.to_display_string()
            assert len(content_str) > 0, "Content should not be empty"

        # Verify we find the reference in utils.clj with context
        utils_refs = [ref for ref in result if ref.source_file_path and "utils.clj" in ref.source_file_path]
        assert len(utils_refs) > 0, "Should find reference in utils.clj"

        # The context should contain the calculate-area function
        utils_content = utils_refs[0].to_display_string()
        assert "calculate-area" in utils_content

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_request_full_symbol_tree(self, language_server: SolidLanguageServer):
        """Test retrieving the full symbol tree for project overview
        We just check that we find some expected symbols.
        """
        result = language_server.request_full_symbol_tree()

        assert result is not None, "Should return symbol tree"
        assert isinstance(result, list), "Symbol tree should be a list"
        assert len(result) > 0, "Should find symbols in the project"

        def traverse_symbols(symbols, indent=0):
            """Recursively traverse symbols to print their structure"""
            info = []
            for s in symbols:
                name = getattr(s, "name", "NO_NAME")
                kind = getattr(s, "kind", "NO_KIND")
                info.append(f"{' ' * indent}Symbol: {name}, Kind: {kind}")
                if hasattr(s, "children") and s.children:
                    info.append(" " * indent + "Children:")
                    info.extend(traverse_symbols(s.children, indent + 2))
            return info

        def list_all_symbols(symbols: list[UnifiedSymbolInformation]):
            found = []
            for symbol in symbols:
                found.append(symbol["name"])
                found.extend(list_all_symbols(symbol["children"]))
            return found

        all_symbol_names = list_all_symbols(result)

        expected_symbols = ["greet", "add", "multiply", "-main", "calculate-area", "format-greeting", "sum-list"]
        found_expected = [name for name in expected_symbols if any(name in symbol_name for symbol_name in all_symbol_names)]

        if len(found_expected) < 7:
            pytest.fail(
                f"Expected to find at least 3 symbols from {expected_symbols}, but found: {found_expected}.\n"
                f"All symbol names: {all_symbol_names}\n"
                f"Symbol tree structure:\n{traverse_symbols(result)}"
            )

    @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
    def test_request_referencing_symbols(self, language_server: SolidLanguageServer):
        """Test finding symbols that reference a given symbol
        Finds references to the 'multiply' function.
        """
        result = language_server.request_referencing_symbols(CORE_PATH, 12, 6)
        assert isinstance(result, list) and len(result) > 0, "Should find at least one referencing symbol"
        found_relevant_references = False
        for ref in result:
            if hasattr(ref, "symbol") and "calculate-area" in ref.symbol["name"]:
                found_relevant_references = True
                break

        assert found_relevant_references, f"Should have found calculate-area referencing multiply, but got: {result}"


class TestProjectBasics:
    @pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True)
    def test_retrieve_content_around_line(self, project: Project):
        """Test retrieving content around specific lines"""
        # Test retrieving content around the greet function definition (line 2)
        result = project.retrieve_content_around_line(CORE_PATH, 2, 2)

        assert result is not None, "Should retrieve content around line 2"
        content_str = result.to_display_string()
        assert "greet" in content_str, "Should contain the greet function definition"
        assert "defn" in content_str, "Should contain defn keyword"

        # Test retrieving content around multiply function (around line 13)
        result = project.retrieve_content_around_line(CORE_PATH, 13, 1)

        assert result is not None, "Should retrieve content around line 13"
        content_str = result.to_display_string()
        assert "multiply" in content_str, "Should contain multiply function"

    @pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True)
    def test_search_files_for_pattern(self, project: Project) -> None:
        result = project.search_source_files_for_pattern("defn.*greet")

        assert result is not None, "Pattern search should return results"
        assert len(result) > 0, "Should find at least one match for 'defn.*greet'"

        core_matches = [match for match in result if match.source_file_path and "core.clj" in match.source_file_path]
        assert len(core_matches) > 0, "Should find greet function in core.clj"

        result = project.search_source_files_for_pattern(":require")

        assert result is not None, "Should find require statements"
        utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path]
        assert len(utils_matches) > 0, "Should find require statement in utils.clj"

```

--------------------------------------------------------------------------------
/src/solidlsp/ls_types.py:
--------------------------------------------------------------------------------

```python
"""
Defines wrapper objects around the types returned by LSP to ensure decoupling between LSP versions and multilspy
"""

from __future__ import annotations

from enum import Enum, IntEnum
from typing import NotRequired, Union

from typing_extensions import TypedDict

URI = str
DocumentUri = str
Uint = int
RegExp = str


class Position(TypedDict):
    r"""Position in a text document expressed as zero-based line and character
    offset. Prior to 3.17 the offsets were always based on a UTF-16 string
    representation. So a string of the form `a𐐀b` the character offset of the
    character `a` is 0, the character offset of `𐐀` is 1 and the character
    offset of b is 3 since `𐐀` is represented using two code units in UTF-16.
    Since 3.17 clients and servers can agree on a different string encoding
    representation (e.g. UTF-8). The client announces it's supported encoding
    via the client capability [`general.positionEncodings`](#clientCapabilities).
    The value is an array of position encodings the client supports, with
    decreasing preference (e.g. the encoding at index `0` is the most preferred
    one). To stay backwards compatible the only mandatory encoding is UTF-16
    represented via the string `utf-16`. The server can pick one of the
    encodings offered by the client and signals that encoding back to the
    client via the initialize result's property
    [`capabilities.positionEncoding`](#serverCapabilities). If the string value
    `utf-16` is missing from the client's capability `general.positionEncodings`
    servers can safely assume that the client supports UTF-16. If the server
    omits the position encoding in its initialize result the encoding defaults
    to the string value `utf-16`. Implementation considerations: since the
    conversion from one encoding into another requires the content of the
    file / line the conversion is best done where the file is read which is
    usually on the server side.

    Positions are line end character agnostic. So you can not specify a position
    that denotes `\r|\n` or `\n|` where `|` represents the character offset.

    @since 3.17.0 - support for negotiated position encoding.
    """

    line: Uint
    """ Line position in a document (zero-based).

    If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document.
    If a line number is negative, it defaults to 0. """
    character: Uint
    """ Character offset on a line in a document (zero-based).

    The meaning of this offset is determined by the negotiated
    `PositionEncodingKind`.

    If the character value is greater than the line length it defaults back to the
    line length. """


class Range(TypedDict):
    """A range in a text document expressed as (zero-based) start and end positions.

    If you want to specify a range that contains a line including the line ending
    character(s) then use an end position denoting the start of the next line.
    For example:
    ```ts
    {
        start: { line: 5, character: 23 }
        end : { line 6, character : 0 }
    }
    ```
    """

    start: Position
    """ The range's start position. """
    end: Position
    """ The range's end position. """


class Location(TypedDict):
    """Represents a location inside a resource, such as a line
    inside a text file.
    """

    uri: DocumentUri
    range: Range
    absolutePath: str
    relativePath: str | None


class CompletionItemKind(IntEnum):
    """The kind of a completion entry."""

    Text = 1
    Method = 2
    Function = 3
    Constructor = 4
    Field = 5
    Variable = 6
    Class = 7
    Interface = 8
    Module = 9
    Property = 10
    Unit = 11
    Value = 12
    Enum = 13
    Keyword = 14
    Snippet = 15
    Color = 16
    File = 17
    Reference = 18
    Folder = 19
    EnumMember = 20
    Constant = 21
    Struct = 22
    Event = 23
    Operator = 24
    TypeParameter = 25


class CompletionItem(TypedDict):
    """A completion item represents a text snippet that is
    proposed to complete text that is being typed.
    """

    completionText: str
    """ The completionText of this completion item.

    The completionText property is also by default the text that
    is inserted when selecting this completion."""

    kind: CompletionItemKind
    """ The kind of this completion item. Based of the kind
    an icon is chosen by the editor. """

    detail: NotRequired[str]
    """ A human-readable string with additional information
    about this item, like type or symbol information. """


class SymbolKind(IntEnum):
    """A symbol kind."""

    File = 1
    Module = 2
    Namespace = 3
    Package = 4
    Class = 5
    Method = 6
    Property = 7
    Field = 8
    Constructor = 9
    Enum = 10
    Interface = 11
    Function = 12
    Variable = 13
    Constant = 14
    String = 15
    Number = 16
    Boolean = 17
    Array = 18
    Object = 19
    Key = 20
    Null = 21
    EnumMember = 22
    Struct = 23
    Event = 24
    Operator = 25
    TypeParameter = 26


class SymbolTag(IntEnum):
    """Symbol tags are extra annotations that tweak the rendering of a symbol.

    @since 3.16
    """

    Deprecated = 1
    """ Render a symbol as obsolete, usually using a strike-out. """


class UnifiedSymbolInformation(TypedDict):
    """Represents information about programming constructs like variables, classes,
    interfaces etc.
    """

    deprecated: NotRequired[bool]
    """ Indicates if this symbol is deprecated.

    @deprecated Use tags instead """
    location: NotRequired[Location]
    """ The location of this symbol. The location's range is used by a tool
    to reveal the location in the editor. If the symbol is selected in the
    tool the range's start information is used to position the cursor. So
    the range usually spans more than the actual symbol's name and does
    normally include things like visibility modifiers.

    The range doesn't have to denote a node range in the sense of an abstract
    syntax tree. It can therefore not be used to re-construct a hierarchy of
    the symbols. """
    name: str
    """ The name of this symbol. """
    kind: SymbolKind
    """ The kind of this symbol. """
    tags: NotRequired[list[SymbolTag]]
    """ Tags for this symbol.

    @since 3.16.0 """
    containerName: NotRequired[str]
    """ The name of the symbol containing this symbol. This information is for
    user interface purposes (e.g. to render a qualifier in the user interface
    if necessary). It can't be used to re-infer a hierarchy for the document
    symbols. 
    
    Note: within Serena, the parent attribute was added and should be used instead. 
    Most LS don't provide containerName.
    """

    detail: NotRequired[str]
    """ More detail for this symbol, e.g the signature of a function. """

    range: NotRequired[Range]
    """ The range enclosing this symbol not including leading/trailing whitespace but everything else
    like comments. This information is typically used to determine if the clients cursor is
    inside the symbol to reveal in the symbol in the UI. """
    selectionRange: NotRequired[Range]
    """ The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
    Must be contained by the `range`. """

    body: NotRequired[str]
    """ The body of the symbol. """

    children: list[UnifiedSymbolInformation]
    """ The children of the symbol. 
    Added to be compatible with `lsp_types.DocumentSymbol`, 
    since it is sometimes useful to have the children of the symbol as a user-facing feature."""

    parent: NotRequired[UnifiedSymbolInformation | None]
    """The parent of the symbol, if there is any. Added with Serena, not part of the LSP.
    All symbols except the root packages will have a parent.
    """


class MarkupKind(Enum):
    """Describes the content type that a client supports in various
    result literals like `Hover`, `ParameterInfo` or `CompletionItem`.

    Please note that `MarkupKinds` must not start with a `$`. This kinds
    are reserved for internal usage.
    """

    PlainText = "plaintext"
    """ Plain text is supported as a content format """
    Markdown = "markdown"
    """ Markdown is supported as a content format """


class __MarkedString_Type_1(TypedDict):
    language: str
    value: str


MarkedString = Union[str, "__MarkedString_Type_1"]
""" MarkedString can be used to render human readable text. It is either a markdown string
or a code-block that provides a language and a code snippet. The language identifier
is semantically equal to the optional language identifier in fenced code blocks in GitHub
issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting

The pair of a language and a value is an equivalent to markdown:
```${language}
${value}
```

Note that markdown strings will be sanitized - that means html will be escaped.
@deprecated use MarkupContent instead. """


class MarkupContent(TypedDict):
    r"""A `MarkupContent` literal represents a string value which content is interpreted base on its
    kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds.

    If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues.
    See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting

    Here is an example how such a string can be constructed using JavaScript / TypeScript:
    ```ts
    let markdown: MarkdownContent = {
     kind: MarkupKind.Markdown,
     value: [
       '# Header',
       'Some text',
       '```typescript',
       'someCode();',
       '```'
     ].join('\n')
    };
    ```

    *Please Note* that clients might sanitize the return markdown. A client could decide to
    remove HTML from the markdown to avoid script execution.
    """

    kind: MarkupKind
    """ The type of the Markup """
    value: str
    """ The content itself """


class Hover(TypedDict):
    """The result of a hover request."""

    contents: MarkupContent | MarkedString | list[MarkedString]
    """ The hover's content """
    range: NotRequired[Range]
    """ An optional range inside the text document that is used to
    visualize the hover, e.g. by changing the background color. """


class DiagnosticsSeverity(IntEnum):
    ERROR = 1
    WARNING = 2
    INFORMATION = 3
    HINT = 4


class Diagnostic(TypedDict):
    """Diagnostic information for a text document."""

    uri: DocumentUri
    """ The URI of the text document to which the diagnostics apply. """
    range: Range
    """ The range of the text document to which the diagnostics apply. """
    severity: NotRequired[DiagnosticsSeverity]
    """ The severity of the diagnostic. """
    message: str
    """ The diagnostic message. """
    code: str
    """ The code of the diagnostic. """
    source: NotRequired[str]
    """ The source of the diagnostic, e.g. the name of the tool that produced it. """

```

--------------------------------------------------------------------------------
/src/solidlsp/ls_config.py:
--------------------------------------------------------------------------------

```python
"""
Configuration objects for language servers
"""

import fnmatch
from collections.abc import Iterable
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Self

if TYPE_CHECKING:
    from solidlsp import SolidLanguageServer


class FilenameMatcher:
    def __init__(self, *patterns: str) -> None:
        """
        :param patterns: fnmatch-compatible patterns
        """
        self.patterns = patterns

    def is_relevant_filename(self, fn: str) -> bool:
        for pattern in self.patterns:
            if fnmatch.fnmatch(fn, pattern):
                return True
        return False


class Language(str, Enum):
    """
    Possible languages with Multilspy.
    """

    CSHARP = "csharp"
    PYTHON = "python"
    RUST = "rust"
    JAVA = "java"
    KOTLIN = "kotlin"
    TYPESCRIPT = "typescript"
    GO = "go"
    RUBY = "ruby"
    DART = "dart"
    CPP = "cpp"
    PHP = "php"
    R = "r"
    PERL = "perl"
    CLOJURE = "clojure"
    ELIXIR = "elixir"
    ELM = "elm"
    TERRAFORM = "terraform"
    SWIFT = "swift"
    BASH = "bash"
    ZIG = "zig"
    LUA = "lua"
    NIX = "nix"
    ERLANG = "erlang"
    AL = "al"
    # Experimental or deprecated Language Servers
    TYPESCRIPT_VTS = "typescript_vts"
    """Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
    PYTHON_JEDI = "python_jedi"
    """Jedi language server for Python (instead of pyright, which is the default)"""
    CSHARP_OMNISHARP = "csharp_omnisharp"
    """OmniSharp language server for C# (instead of the default csharp-ls by microsoft).
    Currently has problems with finding references, and generally seems less stable and performant.
    """
    RUBY_SOLARGRAPH = "ruby_solargraph"
    """Solargraph language server for Ruby (legacy, experimental).
    Use Language.RUBY (ruby-lsp) for better performance and modern LSP features.
    """
    MARKDOWN = "markdown"
    """Marksman language server for Markdown (experimental).
    Must be explicitly specified as the main language, not auto-detected.
    This is an edge case primarily useful when working on documentation-heavy projects.
    """

    @classmethod
    def iter_all(cls, include_experimental: bool = False) -> Iterable[Self]:
        for lang in cls:
            if include_experimental or not lang.is_experimental():
                yield lang

    def is_experimental(self) -> bool:
        """
        Check if the language server is experimental or deprecated.
        """
        return self in {self.TYPESCRIPT_VTS, self.PYTHON_JEDI, self.CSHARP_OMNISHARP, self.RUBY_SOLARGRAPH, self.MARKDOWN}

    def __str__(self) -> str:
        return self.value

    def get_source_fn_matcher(self) -> FilenameMatcher:
        match self:
            case self.PYTHON | self.PYTHON_JEDI:
                return FilenameMatcher("*.py", "*.pyi")
            case self.JAVA:
                return FilenameMatcher("*.java")
            case self.TYPESCRIPT | self.TYPESCRIPT_VTS:
                # see https://github.com/oraios/serena/issues/204
                path_patterns = []
                for prefix in ["c", "m", ""]:
                    for postfix in ["x", ""]:
                        for base_pattern in ["ts", "js"]:
                            path_patterns.append(f"*.{prefix}{base_pattern}{postfix}")
                return FilenameMatcher(*path_patterns)
            case self.CSHARP | self.CSHARP_OMNISHARP:
                return FilenameMatcher("*.cs")
            case self.RUST:
                return FilenameMatcher("*.rs")
            case self.GO:
                return FilenameMatcher("*.go")
            case self.RUBY:
                return FilenameMatcher("*.rb", "*.erb")
            case self.RUBY_SOLARGRAPH:
                return FilenameMatcher("*.rb")
            case self.CPP:
                return FilenameMatcher("*.cpp", "*.h", "*.hpp", "*.c", "*.hxx", "*.cc", "*.cxx")
            case self.KOTLIN:
                return FilenameMatcher("*.kt", "*.kts")
            case self.DART:
                return FilenameMatcher("*.dart")
            case self.PHP:
                return FilenameMatcher("*.php")
            case self.R:
                return FilenameMatcher("*.R", "*.r", "*.Rmd", "*.Rnw")
            case self.PERL:
                return FilenameMatcher("*.pl", "*.pm", "*.t")
            case self.CLOJURE:
                return FilenameMatcher("*.clj", "*.cljs", "*.cljc", "*.edn")  # codespell:ignore edn
            case self.ELIXIR:
                return FilenameMatcher("*.ex", "*.exs")
            case self.ELM:
                return FilenameMatcher("*.elm")
            case self.TERRAFORM:
                return FilenameMatcher("*.tf", "*.tfvars", "*.tfstate")
            case self.SWIFT:
                return FilenameMatcher("*.swift")
            case self.BASH:
                return FilenameMatcher("*.sh", "*.bash")
            case self.ZIG:
                return FilenameMatcher("*.zig", "*.zon")
            case self.LUA:
                return FilenameMatcher("*.lua")
            case self.NIX:
                return FilenameMatcher("*.nix")
            case self.ERLANG:
                return FilenameMatcher("*.erl", "*.hrl", "*.escript", "*.config", "*.app", "*.app.src")
            case self.AL:
                return FilenameMatcher("*.al", "*.dal")
            case self.MARKDOWN:
                return FilenameMatcher("*.md", "*.markdown")
            case _:
                raise ValueError(f"Unhandled language: {self}")

    def get_ls_class(self) -> type["SolidLanguageServer"]:
        match self:
            case self.PYTHON:
                from solidlsp.language_servers.pyright_server import PyrightServer

                return PyrightServer
            case self.PYTHON_JEDI:
                from solidlsp.language_servers.jedi_server import JediServer

                return JediServer
            case self.JAVA:
                from solidlsp.language_servers.eclipse_jdtls import EclipseJDTLS

                return EclipseJDTLS
            case self.KOTLIN:
                from solidlsp.language_servers.kotlin_language_server import KotlinLanguageServer

                return KotlinLanguageServer
            case self.RUST:
                from solidlsp.language_servers.rust_analyzer import RustAnalyzer

                return RustAnalyzer
            case self.CSHARP:
                from solidlsp.language_servers.csharp_language_server import CSharpLanguageServer

                return CSharpLanguageServer
            case self.CSHARP_OMNISHARP:
                from solidlsp.language_servers.omnisharp import OmniSharp

                return OmniSharp
            case self.TYPESCRIPT:
                from solidlsp.language_servers.typescript_language_server import TypeScriptLanguageServer

                return TypeScriptLanguageServer
            case self.TYPESCRIPT_VTS:
                from solidlsp.language_servers.vts_language_server import VtsLanguageServer

                return VtsLanguageServer
            case self.GO:
                from solidlsp.language_servers.gopls import Gopls

                return Gopls
            case self.RUBY:
                from solidlsp.language_servers.ruby_lsp import RubyLsp

                return RubyLsp
            case self.RUBY_SOLARGRAPH:
                from solidlsp.language_servers.solargraph import Solargraph

                return Solargraph
            case self.DART:
                from solidlsp.language_servers.dart_language_server import DartLanguageServer

                return DartLanguageServer
            case self.CPP:
                from solidlsp.language_servers.clangd_language_server import ClangdLanguageServer

                return ClangdLanguageServer
            case self.PHP:
                from solidlsp.language_servers.intelephense import Intelephense

                return Intelephense
            case self.PERL:
                from solidlsp.language_servers.perl_language_server import PerlLanguageServer

                return PerlLanguageServer
            case self.CLOJURE:
                from solidlsp.language_servers.clojure_lsp import ClojureLSP

                return ClojureLSP
            case self.ELIXIR:
                from solidlsp.language_servers.elixir_tools.elixir_tools import ElixirTools

                return ElixirTools
            case self.ELM:
                from solidlsp.language_servers.elm_language_server import ElmLanguageServer

                return ElmLanguageServer
            case self.TERRAFORM:
                from solidlsp.language_servers.terraform_ls import TerraformLS

                return TerraformLS
            case self.SWIFT:
                from solidlsp.language_servers.sourcekit_lsp import SourceKitLSP

                return SourceKitLSP
            case self.BASH:
                from solidlsp.language_servers.bash_language_server import BashLanguageServer

                return BashLanguageServer
            case self.ZIG:
                from solidlsp.language_servers.zls import ZigLanguageServer

                return ZigLanguageServer
            case self.NIX:
                from solidlsp.language_servers.nixd_ls import NixLanguageServer

                return NixLanguageServer
            case self.LUA:
                from solidlsp.language_servers.lua_ls import LuaLanguageServer

                return LuaLanguageServer
            case self.ERLANG:
                from solidlsp.language_servers.erlang_language_server import ErlangLanguageServer

                return ErlangLanguageServer
            case self.AL:
                from solidlsp.language_servers.al_language_server import ALLanguageServer

                return ALLanguageServer
            case self.MARKDOWN:
                from solidlsp.language_servers.marksman import Marksman

                return Marksman
            case self.R:
                from solidlsp.language_servers.r_language_server import RLanguageServer

                return RLanguageServer
            case _:
                raise ValueError(f"Unhandled language: {self}")

    @classmethod
    def from_ls_class(cls, ls_class: type["SolidLanguageServer"]) -> Self:
        """
        Get the Language enum value from a SolidLanguageServer class.

        :param ls_class: The SolidLanguageServer class to find the corresponding Language for
        :return: The Language enum value
        :raises ValueError: If the language server class is not supported
        """
        for enum_instance in cls:
            if enum_instance.get_ls_class() == ls_class:
                return enum_instance
        raise ValueError(f"Unhandled language server class: {ls_class}")


@dataclass
class LanguageServerConfig:
    """
    Configuration parameters
    """

    code_language: Language
    trace_lsp_communication: bool = False
    start_independent_lsp_process: bool = True
    ignored_paths: list[str] = field(default_factory=list)
    """Paths, dirs or glob-like patterns. The matching will follow the same logic as for .gitignore entries"""

    @classmethod
    def from_dict(cls, env: dict):
        """
        Create a MultilspyConfig instance from a dictionary
        """
        import inspect

        return cls(**{k: v for k, v in env.items() if k in inspect.signature(cls).parameters})

```

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

```python
"""
Provides Lua specific instantiation of the LanguageServer class using lua-language-server.
"""

import logging
import os
import pathlib
import platform
import shutil
import tarfile
import threading
import zipfile
from pathlib import Path

import requests
from overrides import override

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


class LuaLanguageServer(SolidLanguageServer):
    """
    Provides Lua specific instantiation of the LanguageServer class using lua-language-server.
    """

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        # For Lua projects, we should ignore:
        # - .luarocks: package manager cache
        # - lua_modules: local dependencies
        # - node_modules: if the project has JavaScript components
        return super().is_ignored_dirname(dirname) or dirname in [".luarocks", "lua_modules", "node_modules", "build", "dist", ".cache"]

    @staticmethod
    def _get_lua_ls_path():
        """Get the path to lua-language-server executable."""
        # First check if it's in PATH
        lua_ls = shutil.which("lua-language-server")
        if lua_ls:
            return lua_ls

        # Check common installation locations
        home = Path.home()
        possible_paths = [
            home / ".local" / "bin" / "lua-language-server",
            home / ".serena" / "language_servers" / "lua" / "bin" / "lua-language-server",
            Path("/usr/local/bin/lua-language-server"),
            Path("/opt/lua-language-server/bin/lua-language-server"),
        ]

        # Add Windows-specific paths
        if platform.system() == "Windows":
            possible_paths.extend(
                [
                    home / "AppData" / "Local" / "lua-language-server" / "bin" / "lua-language-server.exe",
                    home / ".serena" / "language_servers" / "lua" / "bin" / "lua-language-server.exe",
                ]
            )

        for path in possible_paths:
            if path.exists():
                return str(path)

        return None

    @staticmethod
    def _download_lua_ls():
        """Download and install lua-language-server if not present."""
        system = platform.system()
        machine = platform.machine().lower()
        lua_ls_version = "3.15.0"

        # Map platform and architecture to download URL
        if system == "Linux":
            if machine in ["x86_64", "amd64"]:
                download_name = f"lua-language-server-{lua_ls_version}-linux-x64.tar.gz"
            elif machine in ["aarch64", "arm64"]:
                download_name = f"lua-language-server-{lua_ls_version}-linux-arm64.tar.gz"
            else:
                raise RuntimeError(f"Unsupported Linux architecture: {machine}")
        elif system == "Darwin":
            if machine in ["x86_64", "amd64"]:
                download_name = f"lua-language-server-{lua_ls_version}-darwin-x64.tar.gz"
            elif machine in ["arm64", "aarch64"]:
                download_name = f"lua-language-server-{lua_ls_version}-darwin-arm64.tar.gz"
            else:
                raise RuntimeError(f"Unsupported macOS architecture: {machine}")
        elif system == "Windows":
            if machine in ["amd64", "x86_64"]:
                download_name = f"lua-language-server-{lua_ls_version}-win32-x64.zip"
            else:
                raise RuntimeError(f"Unsupported Windows architecture: {machine}")
        else:
            raise RuntimeError(f"Unsupported operating system: {system}")

        download_url = f"https://github.com/LuaLS/lua-language-server/releases/download/{lua_ls_version}/{download_name}"

        # Create installation directory
        install_dir = Path.home() / ".serena" / "language_servers" / "lua"
        install_dir.mkdir(parents=True, exist_ok=True)

        # Download the file
        print(f"Downloading lua-language-server from {download_url}...")
        response = requests.get(download_url, stream=True)
        response.raise_for_status()

        # Save and extract
        download_path = install_dir / download_name
        with open(download_path, "wb") as f:
            for chunk in response.iter_content(chunk_size=8192):
                f.write(chunk)

        print(f"Extracting lua-language-server to {install_dir}...")
        if download_name.endswith(".tar.gz"):
            with tarfile.open(download_path, "r:gz") as tar:
                tar.extractall(install_dir)
        elif download_name.endswith(".zip"):
            with zipfile.ZipFile(download_path, "r") as zip_ref:
                zip_ref.extractall(install_dir)

        # Clean up download file
        download_path.unlink()

        # Make executable on Unix systems
        if system != "Windows":
            lua_ls_path = install_dir / "bin" / "lua-language-server"
            if lua_ls_path.exists():
                lua_ls_path.chmod(0o755)
                return str(lua_ls_path)
        else:
            lua_ls_path = install_dir / "bin" / "lua-language-server.exe"
            if lua_ls_path.exists():
                return str(lua_ls_path)

        raise RuntimeError("Failed to find lua-language-server executable after extraction")

    @staticmethod
    def _setup_runtime_dependency():
        """
        Check if required Lua runtime dependencies are available.
        Downloads lua-language-server if not present.
        """
        lua_ls_path = LuaLanguageServer._get_lua_ls_path()

        if not lua_ls_path:
            print("lua-language-server not found. Downloading...")
            lua_ls_path = LuaLanguageServer._download_lua_ls()
            print(f"lua-language-server installed at: {lua_ls_path}")

        return lua_ls_path

    def __init__(
        self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
    ):
        lua_ls_path = self._setup_runtime_dependency()

        super().__init__(
            config,
            logger,
            repository_root_path,
            ProcessLaunchInfo(cmd=lua_ls_path, cwd=repository_root_path),
            "lua",
            solidlsp_settings,
        )
        self.server_ready = threading.Event()
        self.request_id = 0

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Lua Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                    "completion": {
                        "dynamicRegistration": True,
                        "completionItem": {
                            "snippetSupport": True,
                            "commitCharactersSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "deprecatedSupport": True,
                            "preselectSupport": True,
                        },
                    },
                    "hover": {
                        "dynamicRegistration": True,
                        "contentFormat": ["markdown", "plaintext"],
                    },
                    "signatureHelp": {
                        "dynamicRegistration": True,
                        "signatureInformation": {
                            "documentationFormat": ["markdown", "plaintext"],
                            "parameterInformation": {"labelOffsetSupport": True},
                        },
                    },
                },
                "workspace": {
                    "workspaceFolders": True,
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "configuration": True,
                    "symbol": {
                        "dynamicRegistration": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                    },
                },
            },
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
            "initializationOptions": {
                # Lua Language Server specific options
                "runtime": {
                    "version": "Lua 5.4",
                    "path": ["?.lua", "?/init.lua"],
                },
                "diagnostics": {
                    "enable": True,
                    "globals": ["vim", "describe", "it", "before_each", "after_each"],  # Common globals
                },
                "workspace": {
                    "library": [],  # Can be extended with project-specific libraries
                    "checkThirdParty": False,
                    "userThirdParty": [],
                },
                "telemetry": {
                    "enable": False,
                },
                "completion": {
                    "enable": True,
                    "callSnippet": "Both",
                    "keywordSnippet": "Both",
                },
            },
        }
        return initialize_params

    def _start_server(self):
        """Start Lua Language Server process"""

        def register_capability_handler(params):
            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("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

        self.logger.log("Starting Lua Language 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)

        # Verify server capabilities
        assert "textDocumentSync" in init_response["capabilities"]
        assert "definitionProvider" in init_response["capabilities"]
        assert "documentSymbolProvider" in init_response["capabilities"]
        assert "referencesProvider" in init_response["capabilities"]

        self.server.notify.initialized({})
        self.completions_available.set()

        # Lua Language Server is typically ready immediately after initialization
        self.server_ready.set()
        self.server_ready.wait()

```

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

```python
from collections.abc import Generator
from pathlib import Path

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.conftest import create_ls

from . import ERLANG_LS_UNAVAILABLE, ERLANG_LS_UNAVAILABLE_REASON

# These marks will be applied to all tests in this module
pytestmark = [
    pytest.mark.erlang,
    pytest.mark.skipif(ERLANG_LS_UNAVAILABLE, reason=f"Erlang LS not available: {ERLANG_LS_UNAVAILABLE_REASON}"),
]


@pytest.fixture(scope="module")
def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
    """Fixture to set up an LS for the erlang test repo with the 'ignored_dir' directory ignored."""
    ignored_paths = ["_build", "ignored_dir"]
    ls = create_ls(ignored_paths=ignored_paths, language=Language.ERLANG)
    ls.start()
    try:
        yield ls
    finally:
        try:
            ls.stop(shutdown_timeout=1.0)  # Shorter timeout for CI
        except Exception as e:
            print(f"Warning: Error stopping language server: {e}")
            # Force cleanup if needed
            if hasattr(ls, "server") and hasattr(ls.server, "process"):
                try:
                    ls.server.process.terminate()
                except:
                    pass


@pytest.mark.timeout(60)  # Add 60 second timeout
@pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True)
def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
    """Tests that request_full_symbol_tree ignores the configured directory."""
    root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
    root_children = root["children"]
    children_names = {child["name"] for child in root_children}

    # Should have src, include, and test directories, but not _build or ignored_dir
    expected_dirs = {"src", "include", "test"}
    found_expected = expected_dirs.intersection(children_names)
    assert len(found_expected) > 0, f"Expected some dirs from {expected_dirs} to be in {children_names}"
    assert "_build" not in children_names, f"_build should not be in {children_names}"
    assert "ignored_dir" not in children_names, f"ignored_dir should not be in {children_names}"


@pytest.mark.timeout(60)  # Add 60 second timeout
@pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True)
def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
    """Tests that find_references ignores the configured directory."""
    # Location of user record, which might be referenced in ignored_dir
    definition_file = "include/records.hrl"

    # Find the user record definition
    symbols = ls_with_ignored_dirs.request_document_symbols(definition_file)
    user_symbol = None
    for symbol_group in symbols:
        user_symbol = next((s for s in symbol_group if "user" in s.get("name", "").lower()), None)
        if user_symbol:
            break

    if not user_symbol or "selectionRange" not in user_symbol:
        pytest.skip("User record symbol not found for reference testing")

    sel_start = user_symbol["selectionRange"]["start"]
    references = ls_with_ignored_dirs.request_references(definition_file, sel_start["line"], sel_start["character"])

    # Assert that _build and ignored_dir do not appear in the references
    assert not any("_build" in ref["relativePath"] for ref in references), "_build should be ignored"
    assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored"


@pytest.mark.timeout(90)  # Longer timeout for this complex test
@pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
@pytest.mark.parametrize("repo_path", [Language.ERLANG], indirect=True)
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
    """Tests that refs and symbols with glob patterns are ignored."""
    ignored_paths = ["_build*", "ignored_*", "*.tmp"]
    ls = create_ls(ignored_paths=ignored_paths, repo_path=str(repo_path), language=Language.ERLANG)
    ls.start()

    try:
        # Same as in the above tests
        root = ls.request_full_symbol_tree()[0]
        root_children = root["children"]
        children_names = {child["name"] for child in root_children}

        # Should have src, include, and test directories, but not _build or ignored_dir
        expected_dirs = {"src", "include", "test"}
        found_expected = expected_dirs.intersection(children_names)
        assert len(found_expected) > 0, f"Expected some dirs from {expected_dirs} to be in {children_names}"
        assert "_build" not in children_names, f"_build should not be in {children_names} (glob pattern)"
        assert "ignored_dir" not in children_names, f"ignored_dir should not be in {children_names} (glob pattern)"

        # Test that the refs and symbols with glob patterns are ignored
        definition_file = "include/records.hrl"

        # Find the user record definition
        symbols = ls.request_document_symbols(definition_file)
        user_symbol = None
        for symbol_group in symbols:
            user_symbol = next((s for s in symbol_group if "user" in s.get("name", "").lower()), None)
            if user_symbol:
                break

        if user_symbol and "selectionRange" in user_symbol:
            sel_start = user_symbol["selectionRange"]["start"]
            references = ls.request_references(definition_file, sel_start["line"], sel_start["character"])

            # Assert that _build and ignored_dir do not appear in references
            assert not any("_build" in ref["relativePath"] for ref in references), "_build should be ignored (glob)"
            assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored (glob)"
    finally:
        try:
            ls.stop(shutdown_timeout=1.0)  # Shorter timeout for CI
        except Exception as e:
            print(f"Warning: Error stopping glob pattern test LS: {e}")
            # Force cleanup if needed
            if hasattr(ls, "server") and hasattr(ls.server, "process"):
                try:
                    ls.server.process.terminate()
                except:
                    pass


@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
def test_default_ignored_directories(language_server: SolidLanguageServer):
    """Test that default Erlang directories are ignored."""
    # Test that Erlang-specific directories are ignored by default
    assert language_server.is_ignored_dirname("_build"), "_build should be ignored"
    assert language_server.is_ignored_dirname("ebin"), "ebin should be ignored"
    assert language_server.is_ignored_dirname("deps"), "deps should be ignored"
    assert language_server.is_ignored_dirname(".rebar3"), ".rebar3 should be ignored"
    assert language_server.is_ignored_dirname("_checkouts"), "_checkouts should be ignored"
    assert language_server.is_ignored_dirname("node_modules"), "node_modules should be ignored"

    # Test that important directories are not ignored
    assert not language_server.is_ignored_dirname("src"), "src should not be ignored"
    assert not language_server.is_ignored_dirname("include"), "include should not be ignored"
    assert not language_server.is_ignored_dirname("test"), "test should not be ignored"
    assert not language_server.is_ignored_dirname("priv"), "priv should not be ignored"


@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
def test_symbol_tree_excludes_build_dirs(language_server: SolidLanguageServer):
    """Test that symbol tree excludes build and dependency directories."""
    symbol_tree = language_server.request_full_symbol_tree()

    if symbol_tree:
        root = symbol_tree[0]
        children_names = {child["name"] for child in root.get("children", [])}

        # Build and dependency directories should not appear
        ignored_dirs = {"_build", "ebin", "deps", ".rebar3", "_checkouts", "node_modules"}
        found_ignored = ignored_dirs.intersection(children_names)
        assert len(found_ignored) == 0, f"Found ignored directories in symbol tree: {found_ignored}"

        # Important directories should appear
        important_dirs = {"src", "include", "test"}
        found_important = important_dirs.intersection(children_names)
        assert len(found_important) > 0, f"Expected to find important directories: {important_dirs}, got: {children_names}"


@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
def test_ignore_compiled_files(language_server: SolidLanguageServer):
    """Test that compiled Erlang files are ignored."""
    # Test that beam files are ignored
    assert language_server.is_ignored_filename("module.beam"), "BEAM files should be ignored"
    assert language_server.is_ignored_filename("app.beam"), "BEAM files should be ignored"

    # Test that source files are not ignored
    assert not language_server.is_ignored_filename("module.erl"), "Erlang source files should not be ignored"
    assert not language_server.is_ignored_filename("records.hrl"), "Header files should not be ignored"


@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
def test_rebar_directories_ignored(language_server: SolidLanguageServer):
    """Test that rebar-specific directories are ignored."""
    # Test rebar3-specific directories
    assert language_server.is_ignored_dirname("_build"), "rebar3 _build should be ignored"
    assert language_server.is_ignored_dirname("_checkouts"), "rebar3 _checkouts should be ignored"
    assert language_server.is_ignored_dirname(".rebar3"), "rebar3 cache should be ignored"

    # Test that rebar.lock and rebar.config are not ignored (they are configuration files)
    assert not language_server.is_ignored_filename("rebar.config"), "rebar.config should not be ignored"
    assert not language_server.is_ignored_filename("rebar.lock"), "rebar.lock should not be ignored"


@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True)
def test_document_symbols_ignores_dirs(ls_with_ignored_dirs: SolidLanguageServer):
    """Test that document symbols from ignored directories are not included."""
    # Try to get symbols from a file in ignored directory (should not find it)
    try:
        ignored_file = "ignored_dir/ignored_module.erl"
        symbols = ls_with_ignored_dirs.request_document_symbols(ignored_file)
        # If we get here, the file was found - symbols should be empty or None
        if symbols:
            assert len(symbols) == 0, "Should not find symbols in ignored directory"
    except Exception:
        # This is expected - the file should not be accessible
        pass


@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
def test_erlang_specific_ignore_patterns(language_server: SolidLanguageServer):
    """Test Erlang-specific ignore patterns work correctly."""
    erlang_ignored_dirs = ["_build", "ebin", ".rebar3", "_checkouts", "cover"]

    # These should be ignored
    for dirname in erlang_ignored_dirs:
        assert language_server.is_ignored_dirname(dirname), f"{dirname} should be ignored"

    # These should not be ignored
    erlang_important_dirs = ["src", "include", "test", "priv"]
    for dirname in erlang_important_dirs:
        assert not language_server.is_ignored_dirname(dirname), f"{dirname} should not be ignored"

```

--------------------------------------------------------------------------------
/test/solidlsp/lua/test_lua_basic.py:
--------------------------------------------------------------------------------

```python
"""
Tests for the Lua language server implementation.

These tests validate symbol finding and cross-file reference capabilities
for Lua modules and functions.
"""

import pytest

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


@pytest.mark.lua
class TestLuaLanguageServer:
    """Test Lua language server symbol finding and cross-file references."""

    @pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
    def test_find_symbols_in_calculator(self, language_server: SolidLanguageServer) -> None:
        """Test finding specific functions in calculator.lua."""
        symbols = language_server.request_document_symbols("src/calculator.lua")

        assert symbols is not None
        assert len(symbols) > 0

        # Extract function names from the returned structure
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        function_names = set()
        for symbol in symbol_list:
            if isinstance(symbol, dict):
                name = symbol.get("name", "")
                # Handle both plain names and module-prefixed names
                if "." in name:
                    name = name.split(".")[-1]
                if symbol.get("kind") == SymbolKind.Function:
                    function_names.add(name)

        # Verify exact calculator functions exist
        expected_functions = {"add", "subtract", "multiply", "divide", "factorial"}
        found_functions = function_names & expected_functions
        assert found_functions == expected_functions, f"Expected exactly {expected_functions}, found {found_functions}"

        # Verify specific functions
        assert "add" in function_names, "add function not found"
        assert "multiply" in function_names, "multiply function not found"
        assert "factorial" in function_names, "factorial function not found"

    @pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
    def test_find_symbols_in_utils(self, language_server: SolidLanguageServer) -> None:
        """Test finding specific functions in utils.lua."""
        symbols = language_server.request_document_symbols("src/utils.lua")

        assert symbols is not None
        assert len(symbols) > 0

        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        function_names = set()
        all_symbols = set()

        for symbol in symbol_list:
            if isinstance(symbol, dict):
                name = symbol.get("name", "")
                all_symbols.add(name)
                # Handle both plain names and module-prefixed names
                if "." in name:
                    name = name.split(".")[-1]
                if symbol.get("kind") == SymbolKind.Function:
                    function_names.add(name)

        # Verify exact string utility functions
        expected_utils = {"trim", "split", "starts_with", "ends_with"}
        found_utils = function_names & expected_utils
        assert found_utils == expected_utils, f"Expected exactly {expected_utils}, found {found_utils}"

        # Verify exact table utility functions
        table_utils = {"deep_copy", "table_contains", "table_merge"}
        found_table_utils = function_names & table_utils
        assert found_table_utils == table_utils, f"Expected exactly {table_utils}, found {found_table_utils}"

        # Check for Logger class/table
        assert "Logger" in all_symbols or any("Logger" in s for s in all_symbols), "Logger not found in symbols"

    @pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
    def test_find_symbols_in_main(self, language_server: SolidLanguageServer) -> None:
        """Test finding functions in main.lua."""
        symbols = language_server.request_document_symbols("main.lua")

        assert symbols is not None
        assert len(symbols) > 0

        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols
        function_names = set()

        for symbol in symbol_list:
            if isinstance(symbol, dict) and symbol.get("kind") == SymbolKind.Function:
                function_names.add(symbol.get("name", ""))

        # Verify exact main functions exist
        expected_funcs = {"print_banner", "test_calculator", "test_utils"}
        found_funcs = function_names & expected_funcs
        assert found_funcs == expected_funcs, f"Expected exactly {expected_funcs}, found {found_funcs}"

        assert "test_calculator" in function_names, "test_calculator function not found"
        assert "test_utils" in function_names, "test_utils function not found"

    @pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
    def test_cross_file_references_calculator_add(self, language_server: SolidLanguageServer) -> None:
        """Test finding cross-file references to calculator.add function."""
        symbols = language_server.request_document_symbols("src/calculator.lua")

        assert symbols is not None
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols

        # Find the add function
        add_symbol = None
        for sym in symbol_list:
            if isinstance(sym, dict):
                name = sym.get("name", "")
                if "add" in name or name == "add":
                    add_symbol = sym
                    break

        assert add_symbol is not None, "add function not found in calculator.lua"

        # Get references to the add function
        range_info = add_symbol.get("selectionRange", add_symbol.get("range"))
        assert range_info is not None, "add function has no range information"

        range_start = range_info["start"]
        refs = language_server.request_references("src/calculator.lua", range_start["line"], range_start["character"])

        assert refs is not None
        assert isinstance(refs, list)
        # add function appears in: main.lua (lines 16, 71), test_calculator.lua (lines 22, 23, 24)
        # Note: The declaration itself may or may not be included as a reference
        assert len(refs) >= 5, f"Should find at least 5 references to calculator.add, found {len(refs)}"

        # Verify exact reference locations
        ref_files = {}
        for ref in refs:
            filename = ref.get("uri", "").split("/")[-1]
            if filename not in ref_files:
                ref_files[filename] = []
            ref_files[filename].append(ref["range"]["start"]["line"])

        # The declaration may or may not be included
        if "calculator.lua" in ref_files:
            assert (
                5 in ref_files["calculator.lua"]
            ), f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['calculator.lua']}"

        # Check main.lua has usages
        assert "main.lua" in ref_files, "Should find add usages in main.lua"
        assert (
            15 in ref_files["main.lua"] or 70 in ref_files["main.lua"]
        ), f"Should find add usage in main.lua, found at lines {ref_files.get('main.lua', [])}"

        # Check for cross-file references from main.lua
        main_refs = [ref for ref in refs if "main.lua" in ref.get("uri", "")]
        assert len(main_refs) > 0, "calculator.add should be called in main.lua"

    @pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
    def test_cross_file_references_utils_trim(self, language_server: SolidLanguageServer) -> None:
        """Test finding cross-file references to utils.trim function."""
        symbols = language_server.request_document_symbols("src/utils.lua")

        assert symbols is not None
        symbol_list = symbols[0] if isinstance(symbols, tuple) else symbols

        # Find the trim function
        trim_symbol = None
        for sym in symbol_list:
            if isinstance(sym, dict):
                name = sym.get("name", "")
                if "trim" in name or name == "trim":
                    trim_symbol = sym
                    break

        assert trim_symbol is not None, "trim function not found in utils.lua"

        # Get references to the trim function
        range_info = trim_symbol.get("selectionRange", trim_symbol.get("range"))
        assert range_info is not None, "trim function has no range information"

        range_start = range_info["start"]
        refs = language_server.request_references("src/utils.lua", range_start["line"], range_start["character"])

        assert refs is not None
        assert isinstance(refs, list)
        # trim function appears in: usage (line 32 in main.lua)
        # Note: The declaration itself may or may not be included as a reference
        assert len(refs) >= 1, f"Should find at least 1 reference to utils.trim, found {len(refs)}"

        # Verify exact reference locations
        ref_files = {}
        for ref in refs:
            filename = ref.get("uri", "").split("/")[-1]
            if filename not in ref_files:
                ref_files[filename] = []
            ref_files[filename].append(ref["range"]["start"]["line"])

        # The declaration may or may not be included
        if "utils.lua" in ref_files:
            assert (
                5 in ref_files["utils.lua"]
            ), f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['utils.lua']}"

        # Check main.lua has usage
        assert "main.lua" in ref_files, "Should find trim usage in main.lua"
        assert (
            31 in ref_files["main.lua"]
        ), f"Should find trim usage at line 32 (0-indexed: 31) in main.lua, found at lines {ref_files.get('main.lua', [])}"

        # Check for cross-file references from main.lua
        main_refs = [ref for ref in refs if "main.lua" in ref.get("uri", "")]
        assert len(main_refs) > 0, "utils.trim should be called in main.lua"

    @pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
    def test_hover_information(self, language_server: SolidLanguageServer) -> None:
        """Test hover information for symbols."""
        # Get hover info for a function
        hover_info = language_server.request_hover("src/calculator.lua", 5, 10)  # Position near add function

        assert hover_info is not None, "Should provide hover information"

        # Hover info could be a dict with 'contents' or a string
        if isinstance(hover_info, dict):
            assert "contents" in hover_info or "value" in hover_info, "Hover should have contents"

    @pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
    def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
        """Test that full symbol tree is not empty."""
        symbols = language_server.request_full_symbol_tree()

        assert symbols is not None
        assert len(symbols) > 0, "Symbol tree should not be empty"

        # The tree should have at least one root node
        root = symbols[0]
        assert isinstance(root, dict), "Root should be a dict"
        assert "name" in root, "Root should have a name"

    @pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
    def test_references_between_test_and_source(self, language_server: SolidLanguageServer) -> None:
        """Test finding references from test files to source files."""
        # Check if test_calculator.lua references calculator module
        test_symbols = language_server.request_document_symbols("tests/test_calculator.lua")

        assert test_symbols is not None
        assert len(test_symbols) > 0

        # The test file should have some content that references calculator
        symbol_list = test_symbols[0] if isinstance(test_symbols, tuple) else test_symbols
        assert len(symbol_list) > 0, "test_calculator.lua should have symbols"

```

--------------------------------------------------------------------------------
/test/solidlsp/swift/test_swift_basic.py:
--------------------------------------------------------------------------------

```python
"""
Basic integration tests for the Swift language server functionality.

These tests validate the functionality of the language server APIs
like request_references using the Swift test repository.
"""

import os
import platform

import pytest

from serena.project import Project
from serena.text_utils import LineType
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language

# Skip Swift tests on Windows due to complex GitHub Actions configuration
WINDOWS_SKIP = platform.system() == "Windows"
WINDOWS_SKIP_REASON = "GitHub Actions configuration for Swift on Windows is complex, skipping for now."

pytestmark = [pytest.mark.swift, pytest.mark.skipif(WINDOWS_SKIP, reason=WINDOWS_SKIP_REASON)]


class TestSwiftLanguageServerBasics:
    """Test basic functionality of the Swift language server."""

    @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
    def test_goto_definition_calculator_class(self, language_server: SolidLanguageServer) -> None:
        """Test goto_definition on Calculator class usage."""
        file_path = os.path.join("src", "main.swift")

        # Find the Calculator usage at line 5: let calculator = Calculator()
        # Position should be at the "Calculator()" call
        definitions = language_server.request_definition(file_path, 4, 23)  # Position at Calculator() call
        assert isinstance(definitions, list), "Definitions should be a list"
        assert len(definitions) > 0, "Should find definition for Calculator class"

        # Verify the definition points to the Calculator class definition
        calculator_def = definitions[0]
        assert calculator_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"

        # The Calculator class is defined starting at line 16
        start_line = calculator_def.get("range", {}).get("start", {}).get("line")
        assert start_line == 15, f"Calculator class definition should be at line 16, got {start_line + 1}"

    @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
    def test_goto_definition_user_struct(self, language_server: SolidLanguageServer) -> None:
        """Test goto_definition on User struct usage."""
        file_path = os.path.join("src", "main.swift")

        # Find the User usage at line 9: let user = User(name: "Alice", age: 30)
        # Position should be at the "User(...)" call
        definitions = language_server.request_definition(file_path, 8, 18)  # Position at User(...) call
        assert isinstance(definitions, list), "Definitions should be a list"
        assert len(definitions) > 0, "Should find definition for User struct"

        # Verify the definition points to the User struct definition
        user_def = definitions[0]
        assert user_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"

        # The User struct is defined starting at line 26
        start_line = user_def.get("range", {}).get("start", {}).get("line")
        assert start_line == 25, f"User struct definition should be at line 26, got {start_line + 1}"

    @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
    def test_goto_definition_calculator_method(self, language_server: SolidLanguageServer) -> None:
        """Test goto_definition on Calculator method usage."""
        file_path = os.path.join("src", "main.swift")

        # Find the add method usage at line 6: let result = calculator.add(5, 3)
        # Position should be at the "add" method call
        definitions = language_server.request_definition(file_path, 5, 28)  # Position at add method call
        assert isinstance(definitions, list), "Definitions should be a list"

        # Verify the definition points to the add method definition
        add_def = definitions[0]
        assert add_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"

        # The add method is defined starting at line 17
        start_line = add_def.get("range", {}).get("start", {}).get("line")
        assert start_line == 16, f"add method definition should be at line 17, got {start_line + 1}"

    @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
    def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
        """Test goto_definition across files - Utils struct."""
        utils_file = os.path.join("src", "utils.swift")

        # First, let's check if Utils is used anywhere (it might not be in this simple test)
        # We'll test goto_definition on Utils struct itself
        symbols = language_server.request_document_symbols(utils_file)
        utils_symbol = next((s for s in symbols[0] if s.get("name") == "Utils"), None)

        sel_start = utils_symbol["selectionRange"]["start"]
        definitions = language_server.request_definition(utils_file, sel_start["line"], sel_start["character"])
        assert isinstance(definitions, list), "Definitions should be a list"

        # Should find the Utils struct definition itself
        utils_def = definitions[0]
        assert utils_def.get("uri", "").endswith("utils.swift"), "Definition should be in utils.swift"

    @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
    def test_request_references_calculator_class(self, language_server: SolidLanguageServer) -> None:
        """Test request_references on the Calculator class."""
        # Get references to the Calculator class in main.swift
        file_path = os.path.join("src", "main.swift")
        symbols = language_server.request_document_symbols(file_path)

        calculator_symbol = next((s for s in symbols[0] if s.get("name") == "Calculator"), None)

        sel_start = calculator_symbol["selectionRange"]["start"]
        references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert isinstance(references, list), "References should be a list"
        assert len(references) > 0, "Calculator class should be referenced"

        # Validate that Calculator is referenced in the main function
        calculator_refs = [ref for ref in references if ref.get("uri", "").endswith("main.swift")]
        assert len(calculator_refs) > 0, "Calculator class should be referenced in main.swift"

        # Check that one reference is at line 5 (let calculator = Calculator())
        line_5_refs = [ref for ref in calculator_refs if ref.get("range", {}).get("start", {}).get("line") == 4]
        assert len(line_5_refs) > 0, "Calculator should be referenced at line 5"

    @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
    def test_request_references_user_struct(self, language_server: SolidLanguageServer) -> None:
        """Test request_references on the User struct."""
        # Get references to the User struct in main.swift
        file_path = os.path.join("src", "main.swift")
        symbols = language_server.request_document_symbols(file_path)

        user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None)

        sel_start = user_symbol["selectionRange"]["start"]
        references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert isinstance(references, list), "References should be a list"

        # Validate that User is referenced in the main function
        user_refs = [ref for ref in references if ref.get("uri", "").endswith("main.swift")]
        assert len(user_refs) > 0, "User struct should be referenced in main.swift"

        # Check that one reference is at line 9 (let user = User(...))
        line_9_refs = [ref for ref in user_refs if ref.get("range", {}).get("start", {}).get("line") == 8]
        assert len(line_9_refs) > 0, "User should be referenced at line 9"

    @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
    def test_request_references_utils_struct(self, language_server: SolidLanguageServer) -> None:
        """Test request_references on the Utils struct."""
        # Get references to the Utils struct in utils.swift
        file_path = os.path.join("src", "utils.swift")
        symbols = language_server.request_document_symbols(file_path)
        utils_symbol = next((s for s in symbols[0] if s.get("name") == "Utils"), None)
        if not utils_symbol or "selectionRange" not in utils_symbol:
            raise AssertionError("Utils symbol or its selectionRange not found")
        sel_start = utils_symbol["selectionRange"]["start"]
        references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
        assert isinstance(references, list), "References should be a list"
        assert len(references) > 0, "Utils struct should be referenced"

        # Validate that Utils is referenced in main.swift
        utils_refs = [ref for ref in references if ref.get("uri", "").endswith("main.swift")]
        assert len(utils_refs) > 0, "Utils struct should be referenced in main.swift"

        # Check that one reference is at line 12 (Utils.calculateArea call)
        line_12_refs = [ref for ref in utils_refs if ref.get("range", {}).get("start", {}).get("line") == 11]
        assert len(line_12_refs) > 0, "Utils should be referenced at line 12"


class TestSwiftProjectBasics:
    @pytest.mark.parametrize("project", [Language.SWIFT], indirect=True)
    def test_retrieve_content_around_line(self, project: Project) -> None:
        """Test retrieve_content_around_line functionality with various scenarios."""
        file_path = os.path.join("src", "main.swift")

        # Scenario 1: Find Calculator class definition
        calculator_line = None
        for line_num in range(1, 50):  # Search first 50 lines
            try:
                line_content = project.retrieve_content_around_line(file_path, line_num)
                if line_content.lines and "class Calculator" in line_content.lines[0].line_content:
                    calculator_line = line_num
                    break
            except:
                continue

        assert calculator_line is not None, "Calculator class not found"
        line_calc = project.retrieve_content_around_line(file_path, calculator_line)
        assert len(line_calc.lines) == 1
        assert "class Calculator" in line_calc.lines[0].line_content
        assert line_calc.lines[0].line_number == calculator_line
        assert line_calc.lines[0].match_type == LineType.MATCH

        # Scenario 2: Context above and below Calculator class
        with_context_around_calculator = project.retrieve_content_around_line(file_path, calculator_line, 2, 2)
        assert len(with_context_around_calculator.lines) == 5
        assert "class Calculator" in with_context_around_calculator.matched_lines[0].line_content
        assert with_context_around_calculator.num_matched_lines == 1

        # Scenario 3: Search for struct definitions
        struct_pattern = r"struct\s+\w+"
        matches = project.search_source_files_for_pattern(struct_pattern)
        assert len(matches) > 0, "Should find struct definitions"
        # Should find User struct
        user_matches = [m for m in matches if "User" in str(m)]
        assert len(user_matches) > 0, "Should find User struct"

        # Scenario 4: Search for class definitions
        class_pattern = r"class\s+\w+"
        matches = project.search_source_files_for_pattern(class_pattern)
        assert len(matches) > 0, "Should find class definitions"
        # Should find Calculator and Circle classes
        calculator_matches = [m for m in matches if "Calculator" in str(m)]
        circle_matches = [m for m in matches if "Circle" in str(m)]
        assert len(calculator_matches) > 0, "Should find Calculator class"
        assert len(circle_matches) > 0, "Should find Circle class"

        # Scenario 5: Search for enum definitions
        enum_pattern = r"enum\s+\w+"
        matches = project.search_source_files_for_pattern(enum_pattern)
        assert len(matches) > 0, "Should find enum definitions"
        # Should find Status enum
        status_matches = [m for m in matches if "Status" in str(m)]
        assert len(status_matches) > 0, "Should find Status enum"

```

--------------------------------------------------------------------------------
/test/resources/repos/python/test_repo/custom_test/advanced_features.py:
--------------------------------------------------------------------------------

```python
"""
Advanced Python features for testing code parsing capabilities.

This module contains various advanced Python code patterns to ensure
that the code parser can correctly handle them.
"""

from __future__ import annotations

import asyncio
import os
from abc import ABC, abstractmethod
from collections.abc import Callable, Iterable
from contextlib import contextmanager
from dataclasses import dataclass, field
from enum import Enum, Flag, IntEnum, auto
from functools import wraps
from typing import (
    Annotated,
    Any,
    ClassVar,
    Final,
    Generic,
    Literal,
    NewType,
    Protocol,
    TypedDict,
    TypeVar,
)

# Type variables for generics
T = TypeVar("T")
K = TypeVar("K")
V = TypeVar("V")

# Custom types using NewType
UserId = NewType("UserId", str)
ItemId = NewType("ItemId", int)

# Type aliases
PathLike = str | os.PathLike
JsonDict = dict[str, Any]


# TypedDict
class UserDict(TypedDict):
    """TypedDict representing user data."""

    id: str
    name: str
    email: str
    age: int
    roles: list[str]


# Enums
class Status(Enum):
    """Status enum for process states."""

    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"


class Priority(IntEnum):
    """Priority levels for tasks."""

    LOW = 0
    MEDIUM = 5
    HIGH = 10
    CRITICAL = auto()


class Permissions(Flag):
    """Permission flags for access control."""

    NONE = 0
    READ = 1
    WRITE = 2
    EXECUTE = 4
    ALL = READ | WRITE | EXECUTE


# Abstract class with various method types
class BaseProcessor(ABC):
    """Abstract base class for processors with various method patterns."""

    # Class variable with type annotation
    DEFAULT_TIMEOUT: ClassVar[int] = 30
    MAX_RETRIES: Final[int] = 3

    def __init__(self, name: str, config: dict[str, Any] | None = None):
        self.name = name
        self.config = config or {}
        self._status = Status.PENDING

    @property
    def status(self) -> Status:
        """Status property getter."""
        return self._status

    @status.setter
    def status(self, value: Status) -> None:
        """Status property setter."""
        if not isinstance(value, Status):
            raise TypeError(f"Expected Status enum, got {type(value)}")
        self._status = value

    @abstractmethod
    def process(self, data: Any) -> Any:
        """Process the input data."""

    @classmethod
    def create_from_config(cls, config: dict[str, Any]) -> BaseProcessor:
        """Factory classmethod."""
        name = config.get("name", "default")
        return cls(name=name, config=config)

    @staticmethod
    def validate_config(config: dict[str, Any]) -> bool:
        """Static method for config validation."""
        return "name" in config

    def __str__(self) -> str:
        return f"{self.__class__.__name__}(name={self.name})"


# Concrete implementation of abstract class
class DataProcessor(BaseProcessor):
    """Concrete implementation of BaseProcessor."""

    def __init__(self, name: str, config: dict[str, Any] | None = None, priority: Priority = Priority.MEDIUM):
        super().__init__(name, config)
        self.priority = priority
        self.processed_count = 0

    def process(self, data: Any) -> Any:
        """Process the data."""

        # Nested function definition
        def transform(item: Any) -> Any:
            # Nested function within a nested function
            def apply_rules(x: Any) -> Any:
                return x

            return apply_rules(item)

        # Lambda function
        normalize = lambda x: x / max(x) if hasattr(x, "__iter__") and len(x) > 0 else x  # noqa: F841

        result = transform(data)
        self.processed_count += 1
        return result

    # Method with complex type hints
    def batch_process(self, items: list[str | dict[str, Any] | tuple[Any, ...]]) -> dict[str, list[Any]]:
        """Process multiple items in a batch."""
        results: dict[str, list[Any]] = {"success": [], "error": []}

        for item in items:
            try:
                result = self.process(item)
                results["success"].append(result)
            except Exception as e:
                results["error"].append((item, str(e)))

        return results

    # Generator method
    def process_stream(self, data_stream: Iterable[T]) -> Iterable[T]:
        """Process a stream of data, yielding results as they're processed."""
        for item in data_stream:
            yield self.process(item)

    # Async method
    async def async_process(self, data: Any) -> Any:
        """Process data asynchronously."""
        await asyncio.sleep(0.1)
        return self.process(data)

    # Method with function parameters
    def apply_transform(self, data: Any, transform_func: Callable[[Any], Any]) -> Any:
        """Apply a custom transform function to the data."""
        return transform_func(data)


# Dataclass
@dataclass
class Task:
    """Task dataclass for tracking work items."""

    id: str
    name: str
    status: Status = Status.PENDING
    priority: Priority = Priority.MEDIUM
    metadata: dict[str, Any] = field(default_factory=dict)
    dependencies: list[str] = field(default_factory=list)
    created_at: float | None = None

    def __post_init__(self):
        if self.created_at is None:
            import time

            self.created_at = time.time()

    def has_dependencies(self) -> bool:
        """Check if task has dependencies."""
        return len(self.dependencies) > 0


# Generic class
class Repository(Generic[T]):
    """Generic repository for managing collections of items."""

    def __init__(self):
        self.items: dict[str, T] = {}

    def add(self, id: str, item: T) -> None:
        """Add an item to the repository."""
        self.items[id] = item

    def get(self, id: str) -> T | None:
        """Get an item by id."""
        return self.items.get(id)

    def remove(self, id: str) -> bool:
        """Remove an item by id."""
        if id in self.items:
            del self.items[id]
            return True
        return False

    def list_all(self) -> list[T]:
        """List all items."""
        return list(self.items.values())


# Type with Protocol (structural subtyping)
class Serializable(Protocol):
    """Protocol for objects that can be serialized to dict."""

    def to_dict(self) -> dict[str, Any]: ...


#
# Decorator function
def log_execution(func: Callable) -> Callable:
    """Decorator to log function execution."""

    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Executing {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Finished {func.__name__}")
        return result

    return wrapper


# Context manager
@contextmanager
def transaction_context(name: str = "default"):
    """Context manager for transaction-like operations."""
    print(f"Starting transaction: {name}")
    try:
        yield name
        print(f"Committing transaction: {name}")
    except Exception as e:
        print(f"Rolling back transaction: {name}, error: {e}")
        raise


# Function with complex parameter annotations
def advanced_search(
    query: str,
    filters: dict[str, Any] | None = None,
    sort_by: str | None = None,
    sort_order: Literal["asc", "desc"] = "asc",
    page: int = 1,
    page_size: int = 10,
    include_metadata: bool = False,
) -> tuple[list[dict[str, Any]], int]:
    """
    Advanced search function with many parameters.

    Returns search results and total count.
    """
    results = []
    total = 0
    # Simulating search functionality
    return results, total


# Class with nested classes
class OuterClass:
    """Outer class with nested classes and methods."""

    class NestedClass:
        """Nested class inside OuterClass."""

        def __init__(self, value: Any):
            self.value = value

        def get_value(self) -> Any:
            """Get the stored value."""
            return self.value

        class DeeplyNestedClass:
            """Deeply nested class for testing parser depth capabilities."""

            def deep_method(self) -> str:
                """Method in deeply nested class."""
                return "deep"

    def __init__(self, name: str):
        self.name = name
        self.nested = self.NestedClass(name)

    def get_nested(self) -> NestedClass:
        """Get the nested class instance."""
        return self.nested

    # Method with nested functions
    def process_with_nested(self, data: Any) -> Any:
        """Method demonstrating deeply nested function definitions."""

        def level1(x: Any) -> Any:
            """First level nested function."""

            def level2(y: Any) -> Any:
                """Second level nested function."""

                def level3(z: Any) -> Any:
                    """Third level nested function."""
                    return z

                return level3(y)

            return level2(x)

        return level1(data)


# Metaclass example
class Meta(type):
    """Metaclass example for testing advanced class handling."""

    def __new__(mcs, name, bases, attrs):
        print(f"Creating class: {name}")
        return super().__new__(mcs, name, bases, attrs)

    def __init__(cls, name, bases, attrs):
        print(f"Initializing class: {name}")
        super().__init__(name, bases, attrs)


class WithMeta(metaclass=Meta):
    """Class that uses a metaclass."""

    def __init__(self, value: str):
        self.value = value


# Factory function that creates and returns instances
def create_processor(processor_type: str, name: str, config: dict[str, Any] | None = None) -> BaseProcessor:
    """Factory function that creates and returns processor instances."""
    if processor_type == "data":
        return DataProcessor(name, config)
    else:
        raise ValueError(f"Unknown processor type: {processor_type}")


# Nested decorator example
def with_retry(max_retries: int = 3):
    """Decorator factory that creates a retry decorator."""

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_retries - 1:
                        raise
                    print(f"Retrying {func.__name__} after error: {e}")
            return None

        return wrapper

    return decorator


@with_retry(max_retries=5)
def unreliable_operation(data: Any) -> Any:
    """Function that might fail and uses the retry decorator."""
    import random

    if random.random() < 0.5:
        raise RuntimeError("Random failure")
    return data


# Complex type annotation with Annotated
ValidatedString = Annotated[str, "A string that has been validated"]
PositiveInt = Annotated[int, lambda x: x > 0]


def process_validated_data(data: ValidatedString, count: PositiveInt) -> list[str]:
    """Process data with Annotated type hints."""
    return [data] * count


# Example of forward references and string literals in type annotations
class TreeNode:
    """Tree node with forward reference to itself in annotations."""

    def __init__(self, value: Any):
        self.value = value
        self.children: list[TreeNode] = []

    def add_child(self, child: TreeNode) -> None:
        """Add a child node."""
        self.children.append(child)

    def traverse(self) -> list[Any]:
        """Traverse the tree and return all values."""
        result = [self.value]
        for child in self.children:
            result.extend(child.traverse())
        return result


# Main entry point for demonstration
def main() -> None:
    """Main function demonstrating the use of various features."""
    # Create processor
    processor = DataProcessor("test-processor", {"debug": True})

    # Create tasks
    task1 = Task(id="task1", name="First Task")
    task2 = Task(id="task2", name="Second Task", dependencies=["task1"])

    # Create repository
    repo: Repository[Task] = Repository()
    repo.add(task1.id, task1)
    repo.add(task2.id, task2)

    # Process some data
    data = [1, 2, 3, 4, 5]
    result = processor.process(data)  # noqa: F841

    # Use context manager
    with transaction_context("main"):
        # Process more data
        for task in repo.list_all():
            processor.process(task.name)

    # Use advanced search
    _results, _total = advanced_search(query="test", filters={"status": Status.PENDING}, sort_by="priority", page=1, include_metadata=True)

    # Create a tree
    root = TreeNode("root")
    child1 = TreeNode("child1")
    child2 = TreeNode("child2")
    root.add_child(child1)
    root.add_child(child2)
    child1.add_child(TreeNode("grandchild1"))

    print("Done!")


if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/src/serena/code_editor.py:
--------------------------------------------------------------------------------

```python
import json
import logging
import os
from abc import ABC, abstractmethod
from collections.abc import Iterable, Iterator, Reversible
from contextlib import contextmanager
from typing import TYPE_CHECKING, Generic, Optional, TypeVar

from serena.symbol import JetBrainsSymbol, LanguageServerSymbol, LanguageServerSymbolRetriever, PositionInFile, Symbol
from solidlsp import SolidLanguageServer
from solidlsp.ls import LSPFileBuffer
from solidlsp.ls_utils import TextUtils

from .project import Project
from .tools.jetbrains_plugin_client import JetBrainsPluginClient

if TYPE_CHECKING:
    from .agent import SerenaAgent


log = logging.getLogger(__name__)
TSymbol = TypeVar("TSymbol", bound=Symbol)


class CodeEditor(Generic[TSymbol], ABC):
    def __init__(self, project_root: str, agent: Optional["SerenaAgent"] = None) -> None:
        self.project_root = project_root
        self.agent = agent

    class EditedFile(ABC):
        @abstractmethod
        def get_contents(self) -> str:
            """
            :return: the contents of the file.
            """

        @abstractmethod
        def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
            pass

        @abstractmethod
        def insert_text_at_position(self, pos: PositionInFile, text: str) -> None:
            pass

    @contextmanager
    def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
        """
        Context manager for opening a file
        """
        raise NotImplementedError("This method must be overridden for each subclass")

    @contextmanager
    def _edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
        """
        Context manager for editing a file.
        """
        with self._open_file_context(relative_path) as edited_file:
            yield edited_file
            # save the file
            abs_path = os.path.join(self.project_root, relative_path)
            with open(abs_path, "w", encoding="utf-8") as f:
                f.write(edited_file.get_contents())

    @abstractmethod
    def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> TSymbol:
        """
        Finds the unique symbol with the given name in the given file.
        If no such symbol exists, raises a ValueError.

        :param name_path: the name path
        :param relative_file_path: the relative path of the file in which to search for the symbol.
        :return: the unique symbol
        """

    def replace_body(self, name_path: str, relative_file_path: str, body: str) -> None:
        """
        Replaces the body of the symbol with the given name_path in the given file.

        :param name_path: the name path of the symbol to replace.
        :param relative_file_path: the relative path of the file in which the symbol is defined.
        :param body: the new body
        """
        symbol = self._find_unique_symbol(name_path, relative_file_path)
        start_pos = symbol.get_body_start_position_or_raise()
        end_pos = symbol.get_body_end_position_or_raise()

        with self._edited_file_context(relative_file_path) as edited_file:
            # make sure the replacement adds no additional newlines (before or after) - all newlines
            # and whitespace before/after should remain the same, so we strip it entirely
            body = body.strip()

            edited_file.delete_text_between_positions(start_pos, end_pos)
            edited_file.insert_text_at_position(start_pos, body)

    @staticmethod
    def _count_leading_newlines(text: Iterable) -> int:
        cnt = 0
        for c in text:
            if c == "\n":
                cnt += 1
            elif c == "\r":
                continue
            else:
                break
        return cnt

    @classmethod
    def _count_trailing_newlines(cls, text: Reversible) -> int:
        return cls._count_leading_newlines(reversed(text))

    def insert_after_symbol(self, name_path: str, relative_file_path: str, body: str) -> None:
        """
        Inserts content after the symbol with the given name in the given file.
        """
        symbol = self._find_unique_symbol(name_path, relative_file_path)

        # make sure body always ends with at least one newline
        if not body.endswith("\n"):
            body += "\n"

        pos = symbol.get_body_end_position_or_raise()

        # start at the beginning of the next line
        col = 0
        line = pos.line + 1

        # make sure a suitable number of leading empty lines is used (at least 0/1 depending on the symbol type,
        # otherwise as many as the caller wanted to insert)
        original_leading_newlines = self._count_leading_newlines(body)
        body = body.lstrip("\r\n")
        min_empty_lines = 0
        if symbol.is_neighbouring_definition_separated_by_empty_line():
            min_empty_lines = 1
        num_leading_empty_lines = max(min_empty_lines, original_leading_newlines)
        if num_leading_empty_lines:
            body = ("\n" * num_leading_empty_lines) + body

        # make sure the one line break succeeding the original symbol, which we repurposed as prefix via
        # `line += 1`, is replaced
        body = body.rstrip("\r\n") + "\n"

        with self._edited_file_context(relative_file_path) as edited_file:
            edited_file.insert_text_at_position(PositionInFile(line, col), body)

    def insert_before_symbol(self, name_path: str, relative_file_path: str, body: str) -> None:
        """
        Inserts content before the symbol with the given name in the given file.
        """
        symbol = self._find_unique_symbol(name_path, relative_file_path)
        symbol_start_pos = symbol.get_body_start_position_or_raise()

        # insert position is the start of line where the symbol is defined
        line = symbol_start_pos.line
        col = 0

        original_trailing_empty_lines = self._count_trailing_newlines(body) - 1

        # ensure eol is present at end
        body = body.rstrip() + "\n"

        # add suitable number of trailing empty lines after the body (at least 0/1 depending on the symbol type,
        # otherwise as many as the caller wanted to insert)
        min_trailing_empty_lines = 0
        if symbol.is_neighbouring_definition_separated_by_empty_line():
            min_trailing_empty_lines = 1
        num_trailing_newlines = max(min_trailing_empty_lines, original_trailing_empty_lines)
        body += "\n" * num_trailing_newlines

        # apply edit
        with self._edited_file_context(relative_file_path) as edited_file:
            edited_file.insert_text_at_position(PositionInFile(line=line, col=col), body)

    def insert_at_line(self, relative_path: str, line: int, content: str) -> None:
        """
        Inserts content at the given line in the given file.

        :param relative_path: the relative path of the file in which to insert content
        :param line: the 0-based index of the line to insert content at
        :param content: the content to insert
        """
        with self._edited_file_context(relative_path) as edited_file:
            edited_file.insert_text_at_position(PositionInFile(line, 0), content)

    def delete_lines(self, relative_path: str, start_line: int, end_line: int) -> None:
        """
        Deletes lines in the given file.

        :param relative_path: the relative path of the file in which to delete lines
        :param start_line: the 0-based index of the first line to delete (inclusive)
        :param end_line: the 0-based index of the last line to delete (inclusive)
        """
        start_col = 0
        end_line_for_delete = end_line + 1
        end_col = 0
        with self._edited_file_context(relative_path) as edited_file:
            start_pos = PositionInFile(line=start_line, col=start_col)
            end_pos = PositionInFile(line=end_line_for_delete, col=end_col)
            edited_file.delete_text_between_positions(start_pos, end_pos)

    def delete_symbol(self, name_path: str, relative_file_path: str) -> None:
        """
        Deletes the symbol with the given name in the given file.
        """
        symbol = self._find_unique_symbol(name_path, relative_file_path)
        start_pos = symbol.get_body_start_position_or_raise()
        end_pos = symbol.get_body_end_position_or_raise()
        with self._edited_file_context(relative_file_path) as edited_file:
            edited_file.delete_text_between_positions(start_pos, end_pos)


class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):
    def __init__(self, symbol_retriever: LanguageServerSymbolRetriever, agent: Optional["SerenaAgent"] = None):
        super().__init__(project_root=symbol_retriever.get_language_server().repository_root_path, agent=agent)
        self._symbol_retriever = symbol_retriever

    @property
    def _lang_server(self) -> SolidLanguageServer:
        return self._symbol_retriever.get_language_server()

    class EditedFile(CodeEditor.EditedFile):
        def __init__(self, lang_server: SolidLanguageServer, relative_path: str, file_buffer: LSPFileBuffer):
            self._lang_server = lang_server
            self._relative_path = relative_path
            self._file_buffer = file_buffer

        def get_contents(self) -> str:
            return self._file_buffer.contents

        def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
            self._lang_server.delete_text_between_positions(self._relative_path, start_pos.to_lsp_position(), end_pos.to_lsp_position())

        def insert_text_at_position(self, pos: PositionInFile, text: str) -> None:
            self._lang_server.insert_text_at_position(self._relative_path, pos.line, pos.col, text)

    @contextmanager
    def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
        with self._lang_server.open_file(relative_path) as file_buffer:
            yield self.EditedFile(self._lang_server, relative_path, file_buffer)

    def _get_code_file_content(self, relative_path: str) -> str:
        """Get the content of a file using the language server."""
        return self._lang_server.language_server.retrieve_full_file_content(relative_path)

    def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> LanguageServerSymbol:
        symbol_candidates = self._symbol_retriever.find_by_name(name_path, within_relative_path=relative_file_path)
        if len(symbol_candidates) == 0:
            raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}")
        if len(symbol_candidates) > 1:
            raise ValueError(
                f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. "
                "Their locations are: \n " + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2)
            )
        return symbol_candidates[0]


class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]):
    def __init__(self, project: Project, agent: Optional["SerenaAgent"] = None) -> None:
        self._project = project
        super().__init__(project_root=project.project_root, agent=agent)

    class EditedFile(CodeEditor.EditedFile):
        def __init__(self, relative_path: str, project: Project):
            path = os.path.join(project.project_root, relative_path)
            log.info("Editing file: %s", path)
            with open(path, encoding=project.project_config.encoding) as f:
                self._content = f.read()

        def get_contents(self) -> str:
            return self._content

        def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
            self._content, _ = TextUtils.delete_text_between_positions(
                self._content, start_pos.line, start_pos.col, end_pos.line, end_pos.col
            )

        def insert_text_at_position(self, pos: PositionInFile, text: str) -> None:
            self._content, _, _ = TextUtils.insert_text_at_position(self._content, pos.line, pos.col, text)

    @contextmanager
    def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
        yield self.EditedFile(relative_path, self._project)

    def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> JetBrainsSymbol:
        with JetBrainsPluginClient.from_project(self._project) as client:
            result = client.find_symbol(name_path, relative_path=relative_file_path, include_body=False, depth=0, include_location=True)
            symbols = result["symbols"]
            if not symbols:
                raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}")
            if len(symbols) > 1:
                raise ValueError(
                    f"Found multiple {len(symbols)} symbols with name {name_path} in file {relative_file_path}. "
                    "Their locations are: \n " + json.dumps([s["location"] for s in symbols], indent=2)
                )
            return JetBrainsSymbol(symbols[0], self._project)

```
Page 4/12FirstPrevNextLast