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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/test/solidlsp/vue/test_vue_basic.py:
--------------------------------------------------------------------------------

```python
  1 | import os
  2 | 
  3 | import pytest
  4 | 
  5 | from solidlsp import SolidLanguageServer
  6 | from solidlsp.ls_config import Language
  7 | from solidlsp.ls_utils import SymbolUtils
  8 | 
  9 | 
 10 | @pytest.mark.vue
 11 | class TestVueLanguageServer:
 12 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
 13 |     def test_vue_files_in_symbol_tree(self, language_server: SolidLanguageServer) -> None:
 14 |         symbols = language_server.request_full_symbol_tree()
 15 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "App"), "App not found in symbol tree"
 16 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculatorButton"), "CalculatorButton not found in symbol tree"
 17 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculatorInput"), "CalculatorInput not found in symbol tree"
 18 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculatorDisplay"), "CalculatorDisplay not found in symbol tree"
 19 | 
 20 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
 21 |     def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
 22 |         store_file = os.path.join("src", "stores", "calculator.ts")
 23 |         symbols = language_server.request_document_symbols(store_file).get_all_symbols_and_roots()
 24 | 
 25 |         # Find useCalculatorStore function
 26 |         store_symbol = None
 27 |         for sym in symbols[0]:
 28 |             if sym.get("name") == "useCalculatorStore":
 29 |                 store_symbol = sym
 30 |                 break
 31 | 
 32 |         assert store_symbol is not None, "useCalculatorStore function not found"
 33 | 
 34 |         # Get references
 35 |         sel_start = store_symbol["selectionRange"]["start"]
 36 |         refs = language_server.request_references(store_file, sel_start["line"], sel_start["character"])
 37 | 
 38 |         # Should have multiple references: definition + usage in App.vue, CalculatorInput.vue, CalculatorDisplay.vue
 39 |         assert len(refs) >= 4, f"useCalculatorStore should have at least 4 references (definition + 3 usages), got {len(refs)}"
 40 | 
 41 |         # Verify we have references from .vue files
 42 |         vue_refs = [ref for ref in refs if ".vue" in ref.get("relativePath", "")]
 43 |         assert len(vue_refs) >= 3, f"Should have at least 3 Vue component references, got {len(vue_refs)}"
 44 | 
 45 | 
 46 | @pytest.mark.vue
 47 | class TestVueDualLspArchitecture:
 48 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
 49 |     def test_typescript_server_coordination(self, language_server: SolidLanguageServer) -> None:
 50 |         ts_file = os.path.join("src", "stores", "calculator.ts")
 51 |         ts_symbols = language_server.request_document_symbols(ts_file).get_all_symbols_and_roots()
 52 |         ts_symbol_names = [s.get("name") for s in ts_symbols[0]]
 53 | 
 54 |         assert len(ts_symbols[0]) >= 5, f"TypeScript server should return multiple symbols for calculator.ts, got {len(ts_symbols[0])}"
 55 |         assert "useCalculatorStore" in ts_symbol_names, "TypeScript server should extract store function"
 56 | 
 57 |         # Verify Vue server can parse .vue files
 58 |         vue_file = os.path.join("src", "App.vue")
 59 |         vue_symbols = language_server.request_document_symbols(vue_file).get_all_symbols_and_roots()
 60 |         vue_symbol_names = [s.get("name") for s in vue_symbols[0]]
 61 | 
 62 |         assert len(vue_symbols[0]) >= 15, f"Vue server should return at least 15 symbols for App.vue, got {len(vue_symbols[0])}"
 63 |         assert "appTitle" in vue_symbol_names, "Vue server should extract ref declarations from script setup"
 64 | 
 65 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
 66 |     def test_cross_file_references_vue_to_typescript(self, language_server: SolidLanguageServer) -> None:
 67 |         store_file = os.path.join("src", "stores", "calculator.ts")
 68 |         store_symbols = language_server.request_document_symbols(store_file).get_all_symbols_and_roots()
 69 | 
 70 |         store_symbol = None
 71 |         for sym in store_symbols[0]:
 72 |             if sym.get("name") == "useCalculatorStore":
 73 |                 store_symbol = sym
 74 |                 break
 75 | 
 76 |         if not store_symbol or "selectionRange" not in store_symbol:
 77 |             pytest.skip("useCalculatorStore symbol not found - test fixture may need updating")
 78 | 
 79 |         # Request references for this symbol
 80 |         sel_start = store_symbol["selectionRange"]["start"]
 81 |         refs = language_server.request_references(store_file, sel_start["line"], sel_start["character"])
 82 | 
 83 |         # Verify we found references: definition + usage in App.vue, CalculatorInput.vue, CalculatorDisplay.vue
 84 |         assert len(refs) >= 4, f"useCalculatorStore should have at least 4 references (definition + 3 usages), found {len(refs)} references"
 85 | 
 86 |         # Verify references include .vue files (components that import the store)
 87 |         vue_refs = [ref for ref in refs if ".vue" in ref.get("uri", "")]
 88 |         assert (
 89 |             len(vue_refs) >= 3
 90 |         ), f"Should find at least 3 references in Vue components, found {len(vue_refs)}: {[ref.get('uri', '') for ref in vue_refs]}"
 91 | 
 92 |         # Verify specific components that use the store
 93 |         expected_vue_files = ["App.vue", "CalculatorInput.vue", "CalculatorDisplay.vue"]
 94 |         found_components = []
 95 |         for expected_file in expected_vue_files:
 96 |             matching_refs = [ref for ref in vue_refs if expected_file in ref.get("uri", "")]
 97 |             if matching_refs:
 98 |                 found_components.append(expected_file)
 99 | 
100 |         assert len(found_components) > 0, (
101 |             f"Should find references in at least one component that uses the store. "
102 |             f"Expected any of {expected_vue_files}, found references in: {[ref.get('uri', '') for ref in vue_refs]}"
103 |         )
104 | 
105 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
106 |     def test_cross_file_references_typescript_to_vue(self, language_server: SolidLanguageServer) -> None:
107 |         types_file = os.path.join("src", "types", "index.ts")
108 |         types_symbols = language_server.request_document_symbols(types_file).get_all_symbols_and_roots()
109 |         types_symbol_names = [s.get("name") for s in types_symbols[0]]
110 | 
111 |         # Operation type is used in calculator.ts and CalculatorInput.vue
112 |         assert "Operation" in types_symbol_names, "Operation type should exist in types file"
113 | 
114 |         operation_symbol = None
115 |         for sym in types_symbols[0]:
116 |             if sym.get("name") == "Operation":
117 |                 operation_symbol = sym
118 |                 break
119 | 
120 |         if not operation_symbol or "selectionRange" not in operation_symbol:
121 |             pytest.skip("Operation type symbol not found - test fixture may need updating")
122 | 
123 |         # Request references for the Operation type
124 |         sel_start = operation_symbol["selectionRange"]["start"]
125 |         refs = language_server.request_references(types_file, sel_start["line"], sel_start["character"])
126 | 
127 |         # Verify we found references: definition + usage in calculator.ts and Vue files
128 |         assert len(refs) >= 2, f"Operation type should have at least 2 references (definition + usages), found {len(refs)} references"
129 | 
130 |         # The Operation type should be referenced in both .ts files (calculator.ts) and potentially .vue files
131 |         all_ref_uris = [ref.get("uri", "") for ref in refs]
132 |         has_ts_refs = any(".ts" in uri and "types" not in uri for uri in all_ref_uris)
133 | 
134 |         assert (
135 |             has_ts_refs
136 |         ), f"Operation type should be referenced in TypeScript files like calculator.ts. Found references in: {all_ref_uris}"
137 | 
138 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
139 |     def test_reference_deduplication(self, language_server: SolidLanguageServer) -> None:
140 |         store_file = os.path.join("src", "stores", "calculator.ts")
141 |         store_symbols = language_server.request_document_symbols(store_file).get_all_symbols_and_roots()
142 | 
143 |         # Find a commonly-used symbol (useCalculatorStore)
144 |         store_symbol = None
145 |         for sym in store_symbols[0]:
146 |             if sym.get("name") == "useCalculatorStore":
147 |                 store_symbol = sym
148 |                 break
149 | 
150 |         if not store_symbol or "selectionRange" not in store_symbol:
151 |             pytest.skip("useCalculatorStore symbol not found - test fixture may need updating")
152 | 
153 |         # Request references
154 |         sel_start = store_symbol["selectionRange"]["start"]
155 |         refs = language_server.request_references(store_file, sel_start["line"], sel_start["character"])
156 | 
157 |         # Check for duplicate references (same file, line, and character)
158 |         seen_locations = set()
159 |         duplicates = []
160 | 
161 |         for ref in refs:
162 |             # Create a unique key for this reference location
163 |             uri = ref.get("uri", "")
164 |             if "range" in ref:
165 |                 line = ref["range"]["start"]["line"]
166 |                 character = ref["range"]["start"]["character"]
167 |                 location_key = (uri, line, character)
168 | 
169 |                 if location_key in seen_locations:
170 |                     duplicates.append(location_key)
171 |                 else:
172 |                     seen_locations.add(location_key)
173 | 
174 |         assert len(duplicates) == 0, (
175 |             f"Found {len(duplicates)} duplicate reference locations. "
176 |             f"The dual-LSP architecture should deduplicate references from both servers. "
177 |             f"Duplicates: {duplicates}"
178 |         )
179 | 
180 | 
181 | @pytest.mark.vue
182 | class TestVueEdgeCases:
183 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
184 |     def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None:
185 |         full_tree = language_server.request_full_symbol_tree()
186 | 
187 |         # Helper to extract all file paths from symbol tree
188 |         def extract_paths_from_tree(symbols, paths=None):
189 |             """Recursively extract file paths from symbol tree."""
190 |             if paths is None:
191 |                 paths = []
192 | 
193 |             if isinstance(symbols, list):
194 |                 for symbol in symbols:
195 |                     extract_paths_from_tree(symbol, paths)
196 |             elif isinstance(symbols, dict):
197 |                 # Check if this symbol has a location
198 |                 if "location" in symbols and "uri" in symbols["location"]:
199 |                     uri = symbols["location"]["uri"]
200 |                     # Extract the path after file://
201 |                     if uri.startswith("file://"):
202 |                         file_path = uri[7:]  # Remove "file://"
203 |                         paths.append(file_path)
204 | 
205 |                 # Recurse into children
206 |                 if "children" in symbols:
207 |                     extract_paths_from_tree(symbols["children"], paths)
208 | 
209 |             return paths
210 | 
211 |         all_paths = extract_paths_from_tree(full_tree)
212 | 
213 |         # Verify we have files from expected directories
214 |         # Note: Symbol tree may include duplicate paths (one per symbol in file)
215 |         components_files = list({p for p in all_paths if "components" in p and ".vue" in p})
216 |         stores_files = list({p for p in all_paths if "stores" in p and ".ts" in p})
217 |         composables_files = list({p for p in all_paths if "composables" in p and ".ts" in p})
218 | 
219 |         assert len(components_files) == 3, (
220 |             f"Symbol tree should include exactly 3 unique Vue components (CalculatorButton, CalculatorInput, CalculatorDisplay). "
221 |             f"Found {len(components_files)} unique component files: {[p.split('/')[-1] for p in sorted(components_files)]}"
222 |         )
223 | 
224 |         assert len(stores_files) == 1, (
225 |             f"Symbol tree should include exactly 1 unique store file (calculator.ts). "
226 |             f"Found {len(stores_files)} unique store files: {[p.split('/')[-1] for p in sorted(stores_files)]}"
227 |         )
228 | 
229 |         assert len(composables_files) == 2, (
230 |             f"Symbol tree should include exactly 2 unique composable files (useFormatter.ts, useTheme.ts). "
231 |             f"Found {len(composables_files)} unique composable files: {[p.split('/')[-1] for p in sorted(composables_files)]}"
232 |         )
233 | 
234 |         # Verify specific expected files exist in the tree
235 |         expected_files = [
236 |             "CalculatorButton.vue",
237 |             "CalculatorInput.vue",
238 |             "CalculatorDisplay.vue",
239 |             "App.vue",
240 |             "calculator.ts",
241 |             "useFormatter.ts",
242 |             "useTheme.ts",
243 |         ]
244 | 
245 |         for expected_file in expected_files:
246 |             matching_files = [p for p in all_paths if expected_file in p]
247 |             assert len(matching_files) > 0, f"Expected file '{expected_file}' should be in symbol tree. All paths: {all_paths}"
248 | 
249 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
250 |     def test_document_overview(self, language_server: SolidLanguageServer) -> None:
251 |         app_file = os.path.join("src", "App.vue")
252 |         overview = language_server.request_document_overview(app_file)
253 | 
254 |         # Overview should return a list of top-level symbols
255 |         assert isinstance(overview, list), f"Overview should be a list, got: {type(overview)}"
256 |         assert len(overview) >= 1, f"App.vue should have at least 1 top-level symbol in overview, got {len(overview)}"
257 | 
258 |         # Extract symbol names from overview
259 |         symbol_names = [s.get("name") for s in overview if isinstance(s, dict)]
260 | 
261 |         # Vue LSP returns SFC structure (template/script/style sections) for .vue files
262 |         # This is expected behavior - overview shows the file's high-level structure
263 |         assert (
264 |             len(symbol_names) >= 1
265 |         ), f"Should have at least 1 symbol name in overview (e.g., 'App' or SFC section), got {len(symbol_names)}: {symbol_names}"
266 | 
267 |         # Test overview for a TypeScript file
268 |         store_file = os.path.join("src", "stores", "calculator.ts")
269 |         store_overview = language_server.request_document_overview(store_file)
270 | 
271 |         assert isinstance(store_overview, list), f"Store overview should be a list, got: {type(store_overview)}"
272 |         assert len(store_overview) >= 1, f"calculator.ts should have at least 1 top-level symbol in overview, got {len(store_overview)}"
273 | 
274 |         store_symbol_names = [s.get("name") for s in store_overview if isinstance(s, dict)]
275 |         assert (
276 |             "useCalculatorStore" in store_symbol_names
277 |         ), f"useCalculatorStore should be in store file overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
278 | 
279 |         # Test overview for another Vue component
280 |         button_file = os.path.join("src", "components", "CalculatorButton.vue")
281 |         button_overview = language_server.request_document_overview(button_file)
282 | 
283 |         assert isinstance(button_overview, list), f"Button overview should be a list, got: {type(button_overview)}"
284 |         assert (
285 |             len(button_overview) >= 1
286 |         ), f"CalculatorButton.vue should have at least 1 top-level symbol in overview, got {len(button_overview)}"
287 | 
288 |         # For Vue files, overview provides SFC structure which is useful for navigation
289 |         # The detailed symbols are available via request_document_symbols
290 |         button_symbol_names = [s.get("name") for s in button_overview if isinstance(s, dict)]
291 |         assert len(button_symbol_names) >= 1, (
292 |             f"CalculatorButton.vue should have at least 1 symbol in overview (e.g., 'CalculatorButton' or SFC section). "
293 |             f"Found {len(button_symbol_names)} symbols: {button_symbol_names}"
294 |         )
295 | 
296 |     @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
297 |     def test_directory_overview(self, language_server: SolidLanguageServer) -> None:
298 |         components_dir = os.path.join("src", "components")
299 |         dir_overview = language_server.request_dir_overview(components_dir)
300 | 
301 |         # Directory overview should be a dict mapping file paths to symbol lists
302 |         assert isinstance(dir_overview, dict), f"Directory overview should be a dict, got: {type(dir_overview)}"
303 |         assert len(dir_overview) == 3, f"src/components directory should have exactly 3 files in overview, got {len(dir_overview)}"
304 | 
305 |         # Verify all component files are included
306 |         expected_components = ["CalculatorButton.vue", "CalculatorInput.vue", "CalculatorDisplay.vue"]
307 | 
308 |         for expected_component in expected_components:
309 |             # Find files that match this component name
310 |             matching_files = [path for path in dir_overview.keys() if expected_component in path]
311 |             assert len(matching_files) == 1, (
312 |                 f"Component '{expected_component}' should appear exactly once in directory overview. "
313 |                 f"Found {len(matching_files)} matches. All files: {list(dir_overview.keys())}"
314 |             )
315 | 
316 |             # Verify the matched file has symbols
317 |             file_path = matching_files[0]
318 |             symbols = dir_overview[file_path]
319 |             assert isinstance(symbols, list), f"Symbols for {file_path} should be a list, got {type(symbols)}"
320 |             assert len(symbols) >= 1, f"Component {expected_component} should have at least 1 symbol in overview, got {len(symbols)}"
321 | 
322 |         # Test overview for stores directory
323 |         stores_dir = os.path.join("src", "stores")
324 |         stores_overview = language_server.request_dir_overview(stores_dir)
325 | 
326 |         assert isinstance(stores_overview, dict), f"Stores overview should be a dict, got: {type(stores_overview)}"
327 |         assert (
328 |             len(stores_overview) == 1
329 |         ), f"src/stores directory should have exactly 1 file (calculator.ts) in overview, got {len(stores_overview)}"
330 | 
331 |         # Verify calculator.ts is included
332 |         calculator_files = [path for path in stores_overview.keys() if "calculator.ts" in path]
333 |         assert len(calculator_files) == 1, (
334 |             f"calculator.ts should appear exactly once in stores directory overview. "
335 |             f"Found {len(calculator_files)} matches. All files: {list(stores_overview.keys())}"
336 |         )
337 | 
338 |         # Verify the store file has symbols
339 |         store_path = calculator_files[0]
340 |         store_symbols = stores_overview[store_path]
341 |         store_symbol_names = [s.get("name") for s in store_symbols if isinstance(s, dict)]
342 |         assert (
343 |             "useCalculatorStore" in store_symbol_names
344 |         ), f"calculator.ts should have useCalculatorStore in overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
345 | 
346 |         # Test overview for composables directory
347 |         composables_dir = os.path.join("src", "composables")
348 |         composables_overview = language_server.request_dir_overview(composables_dir)
349 | 
350 |         assert isinstance(composables_overview, dict), f"Composables overview should be a dict, got: {type(composables_overview)}"
351 |         assert (
352 |             len(composables_overview) == 2
353 |         ), f"src/composables directory should have exactly 2 files in overview, got {len(composables_overview)}"
354 | 
355 |         # Verify composable files are included
356 |         expected_composables = ["useFormatter.ts", "useTheme.ts"]
357 |         for expected_composable in expected_composables:
358 |             matching_files = [path for path in composables_overview.keys() if expected_composable in path]
359 |             assert len(matching_files) == 1, (
360 |                 f"Composable '{expected_composable}' should appear exactly once in directory overview. "
361 |                 f"Found {len(matching_files)} matches. All files: {list(composables_overview.keys())}"
362 |             )
363 | 
```

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

```python
  1 | import json
  2 | import logging
  3 | import os
  4 | import re
  5 | import time
  6 | from collections.abc import Iterator
  7 | from contextlib import contextmanager
  8 | from typing import Literal
  9 | 
 10 | import pytest
 11 | 
 12 | from serena.agent import SerenaAgent
 13 | from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig
 14 | from serena.project import Project
 15 | from serena.tools import SUCCESS_RESULT, FindReferencingSymbolsTool, FindSymbolTool, ReplaceContentTool, ReplaceSymbolBodyTool
 16 | from solidlsp.ls_config import Language
 17 | from solidlsp.ls_types import SymbolKind
 18 | from test.conftest import get_repo_path, language_tests_enabled
 19 | from test.solidlsp import clojure as clj
 20 | 
 21 | 
 22 | @pytest.fixture
 23 | def serena_config():
 24 |     """Create an in-memory configuration for tests with test repositories pre-registered."""
 25 |     # Create test projects for all supported languages
 26 |     test_projects = []
 27 |     for language in [
 28 |         Language.PYTHON,
 29 |         Language.GO,
 30 |         Language.JAVA,
 31 |         Language.KOTLIN,
 32 |         Language.RUST,
 33 |         Language.TYPESCRIPT,
 34 |         Language.PHP,
 35 |         Language.CSHARP,
 36 |         Language.CLOJURE,
 37 |         Language.FSHARP,
 38 |         Language.POWERSHELL,
 39 |     ]:
 40 |         repo_path = get_repo_path(language)
 41 |         if repo_path.exists():
 42 |             project_name = f"test_repo_{language}"
 43 |             project = Project(
 44 |                 project_root=str(repo_path),
 45 |                 project_config=ProjectConfig(
 46 |                     project_name=project_name,
 47 |                     languages=[language],
 48 |                     ignored_paths=[],
 49 |                     excluded_tools=set(),
 50 |                     read_only=False,
 51 |                     ignore_all_files_in_gitignore=True,
 52 |                     initial_prompt="",
 53 |                     encoding="utf-8",
 54 |                 ),
 55 |             )
 56 |             test_projects.append(RegisteredProject.from_project_instance(project))
 57 | 
 58 |     config = SerenaConfig(gui_log_window_enabled=False, web_dashboard=False, log_level=logging.ERROR)
 59 |     config.projects = test_projects
 60 |     return config
 61 | 
 62 | 
 63 | def read_project_file(project: Project, relative_path: str) -> str:
 64 |     """Utility function to read a file from the project."""
 65 |     file_path = os.path.join(project.project_root, relative_path)
 66 |     with open(file_path, encoding=project.project_config.encoding) as f:
 67 |         return f.read()
 68 | 
 69 | 
 70 | @contextmanager
 71 | def project_file_modification_context(serena_agent: SerenaAgent, relative_path: str) -> Iterator[None]:
 72 |     """Context manager to modify a project file and revert the changes after use."""
 73 |     project = serena_agent.get_active_project()
 74 |     file_path = os.path.join(project.project_root, relative_path)
 75 | 
 76 |     # Read the original content
 77 |     original_content = read_project_file(project, relative_path)
 78 | 
 79 |     try:
 80 |         yield
 81 |     finally:
 82 |         # Revert to the original content
 83 |         with open(file_path, "w", encoding=project.project_config.encoding) as f:
 84 |             f.write(original_content)
 85 | 
 86 | 
 87 | @pytest.fixture
 88 | def serena_agent(request: pytest.FixtureRequest, serena_config) -> Iterator[SerenaAgent]:
 89 |     language = Language(request.param)
 90 |     if not language_tests_enabled(language):
 91 |         pytest.skip(f"Tests for language {language} are not enabled.")
 92 | 
 93 |     project_name = f"test_repo_{language}"
 94 | 
 95 |     agent = SerenaAgent(project=project_name, serena_config=serena_config)
 96 | 
 97 |     # wait for agent to be ready
 98 |     agent.execute_task(lambda: None)
 99 | 
100 |     yield agent
101 | 
102 |     # explicitly shut down to free resources
103 |     agent.shutdown(timeout=5)
104 | 
105 | 
106 | class TestSerenaAgent:
107 |     @pytest.mark.parametrize(
108 |         "serena_agent,symbol_name,expected_kind,expected_file",
109 |         [
110 |             pytest.param(Language.PYTHON, "User", "Class", "models.py", marks=pytest.mark.python),
111 |             pytest.param(Language.GO, "Helper", "Function", "main.go", marks=pytest.mark.go),
112 |             pytest.param(Language.JAVA, "Model", "Class", "Model.java", marks=pytest.mark.java),
113 |             pytest.param(Language.KOTLIN, "Model", "Struct", "Model.kt", marks=pytest.mark.kotlin),
114 |             pytest.param(Language.RUST, "add", "Function", "lib.rs", marks=pytest.mark.rust),
115 |             pytest.param(Language.TYPESCRIPT, "DemoClass", "Class", "index.ts", marks=pytest.mark.typescript),
116 |             pytest.param(Language.PHP, "helperFunction", "Function", "helper.php", marks=pytest.mark.php),
117 |             pytest.param(Language.CLOJURE, "greet", "Function", clj.CORE_PATH, marks=pytest.mark.clojure),
118 |             pytest.param(Language.CSHARP, "Calculator", "Class", "Program.cs", marks=pytest.mark.csharp),
119 |             pytest.param(Language.FSHARP, "Calculator", "Module", "Calculator.fs", marks=pytest.mark.fsharp),
120 |             pytest.param(Language.POWERSHELL, "function Greet-User ()", "Function", "main.ps1", marks=pytest.mark.powershell),
121 |         ],
122 |         indirect=["serena_agent"],
123 |     )
124 |     def test_find_symbol(self, serena_agent: SerenaAgent, symbol_name: str, expected_kind: str, expected_file: str):
125 |         agent = serena_agent
126 |         find_symbol_tool = agent.get_tool(FindSymbolTool)
127 |         result = find_symbol_tool.apply_ex(name_path_pattern=symbol_name)
128 | 
129 |         symbols = json.loads(result)
130 |         assert any(
131 |             symbol_name in s["name_path"] and expected_kind.lower() in s["kind"].lower() and expected_file in s["relative_path"]
132 |             for s in symbols
133 |         ), f"Expected to find {symbol_name} ({expected_kind}) in {expected_file}"
134 |         # testing retrieval of symbol info
135 |         if serena_agent.get_active_lsp_languages() == [Language.KOTLIN]:
136 |             # kotlin LS doesn't seem to provide hover info right now, at least for the struct we test this on
137 |             return
138 |         for s in symbols:
139 |             if s["kind"] in (SymbolKind.File.name, SymbolKind.Module.name):
140 |                 # we ignore file and module symbols for the info test
141 |                 continue
142 |             symbol_info = s.get("info")
143 |             assert symbol_info, f"Expected symbol info to be present for symbol: {s}"
144 |             assert (
145 |                 symbol_name in s["info"]
146 |             ), f"[{serena_agent.get_active_lsp_languages()[0]}] Expected symbol info to contain symbol name {symbol_name}. Info: {s['info']}"
147 |             # special additional test for Java, since Eclipse returns hover in a complex format and we want to make sure to get it right
148 |             if s["kind"] == SymbolKind.Class.name and serena_agent.get_active_lsp_languages() == [Language.JAVA]:
149 |                 assert "A simple model class" in symbol_info, f"Java class docstring not found in symbol info: {s}"
150 | 
151 |     @pytest.mark.parametrize(
152 |         "serena_agent,symbol_name,def_file,ref_file",
153 |         [
154 |             pytest.param(
155 |                 Language.PYTHON,
156 |                 "User",
157 |                 os.path.join("test_repo", "models.py"),
158 |                 os.path.join("test_repo", "services.py"),
159 |                 marks=pytest.mark.python,
160 |             ),
161 |             pytest.param(Language.GO, "Helper", "main.go", "main.go", marks=pytest.mark.go),
162 |             pytest.param(
163 |                 Language.JAVA,
164 |                 "Model",
165 |                 os.path.join("src", "main", "java", "test_repo", "Model.java"),
166 |                 os.path.join("src", "main", "java", "test_repo", "Main.java"),
167 |                 marks=pytest.mark.java,
168 |             ),
169 |             pytest.param(
170 |                 Language.KOTLIN,
171 |                 "Model",
172 |                 os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"),
173 |                 os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"),
174 |                 marks=pytest.mark.kotlin,
175 |             ),
176 |             pytest.param(Language.RUST, "add", os.path.join("src", "lib.rs"), os.path.join("src", "main.rs"), marks=pytest.mark.rust),
177 |             pytest.param(Language.TYPESCRIPT, "helperFunction", "index.ts", "use_helper.ts", marks=pytest.mark.typescript),
178 |             pytest.param(Language.PHP, "helperFunction", "helper.php", "index.php", marks=pytest.mark.php),
179 |             pytest.param(
180 |                 Language.CLOJURE,
181 |                 "multiply",
182 |                 clj.CORE_PATH,
183 |                 clj.UTILS_PATH,
184 |                 marks=pytest.mark.clojure,
185 |             ),
186 |             pytest.param(Language.CSHARP, "Calculator", "Program.cs", "Program.cs", marks=pytest.mark.csharp),
187 |             pytest.param(Language.FSHARP, "add", "Calculator.fs", "Program.fs", marks=pytest.mark.fsharp),
188 |             pytest.param(Language.POWERSHELL, "function Greet-User ()", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
189 |         ],
190 |         indirect=["serena_agent"],
191 |     )
192 |     def test_find_symbol_references(self, serena_agent: SerenaAgent, symbol_name: str, def_file: str, ref_file: str) -> None:
193 |         agent = serena_agent
194 | 
195 |         # Find the symbol location first
196 |         find_symbol_tool = agent.get_tool(FindSymbolTool)
197 |         result = find_symbol_tool.apply_ex(name_path_pattern=symbol_name, relative_path=def_file)
198 | 
199 |         time.sleep(1)
200 |         symbols = json.loads(result)
201 |         # Find the definition
202 |         def_symbol = symbols[0]
203 | 
204 |         # Now find references
205 |         find_refs_tool = agent.get_tool(FindReferencingSymbolsTool)
206 |         result = find_refs_tool.apply_ex(name_path=def_symbol["name_path"], relative_path=def_symbol["relative_path"])
207 | 
208 |         refs = json.loads(result)
209 |         assert any(
210 |             ref["relative_path"] == ref_file for ref in refs
211 |         ), f"Expected to find reference to {symbol_name} in {ref_file}. refs={refs}"
212 | 
213 |     @pytest.mark.parametrize(
214 |         "serena_agent,name_path,substring_matching,expected_symbol_name,expected_kind,expected_file",
215 |         [
216 |             pytest.param(
217 |                 Language.PYTHON,
218 |                 "OuterClass/NestedClass",
219 |                 False,
220 |                 "NestedClass",
221 |                 "Class",
222 |                 os.path.join("test_repo", "nested.py"),
223 |                 id="exact_qualname_class",
224 |                 marks=pytest.mark.python,
225 |             ),
226 |             pytest.param(
227 |                 Language.PYTHON,
228 |                 "OuterClass/NestedClass/find_me",
229 |                 False,
230 |                 "find_me",
231 |                 "Method",
232 |                 os.path.join("test_repo", "nested.py"),
233 |                 id="exact_qualname_method",
234 |                 marks=pytest.mark.python,
235 |             ),
236 |             pytest.param(
237 |                 Language.PYTHON,
238 |                 "OuterClass/NestedCl",  # Substring for NestedClass
239 |                 True,
240 |                 "NestedClass",
241 |                 "Class",
242 |                 os.path.join("test_repo", "nested.py"),
243 |                 id="substring_qualname_class",
244 |                 marks=pytest.mark.python,
245 |             ),
246 |             pytest.param(
247 |                 Language.PYTHON,
248 |                 "OuterClass/NestedClass/find_m",  # Substring for find_me
249 |                 True,
250 |                 "find_me",
251 |                 "Method",
252 |                 os.path.join("test_repo", "nested.py"),
253 |                 id="substring_qualname_method",
254 |                 marks=pytest.mark.python,
255 |             ),
256 |             pytest.param(
257 |                 Language.PYTHON,
258 |                 "/OuterClass",  # Absolute path
259 |                 False,
260 |                 "OuterClass",
261 |                 "Class",
262 |                 os.path.join("test_repo", "nested.py"),
263 |                 id="absolute_qualname_class",
264 |                 marks=pytest.mark.python,
265 |             ),
266 |             pytest.param(
267 |                 Language.PYTHON,
268 |                 "/OuterClass/NestedClass/find_m",  # Absolute path with substring
269 |                 True,
270 |                 "find_me",
271 |                 "Method",
272 |                 os.path.join("test_repo", "nested.py"),
273 |                 id="absolute_substring_qualname_method",
274 |                 marks=pytest.mark.python,
275 |             ),
276 |         ],
277 |         indirect=["serena_agent"],
278 |     )
279 |     def test_find_symbol_name_path(
280 |         self,
281 |         serena_agent,
282 |         name_path: str,
283 |         substring_matching: bool,
284 |         expected_symbol_name: str,
285 |         expected_kind: str,
286 |         expected_file: str,
287 |     ):
288 |         agent = serena_agent
289 | 
290 |         find_symbol_tool = agent.get_tool(FindSymbolTool)
291 |         result = find_symbol_tool.apply_ex(
292 |             name_path_pattern=name_path,
293 |             depth=0,
294 |             relative_path=None,
295 |             include_body=False,
296 |             include_kinds=None,
297 |             exclude_kinds=None,
298 |             substring_matching=substring_matching,
299 |         )
300 | 
301 |         symbols = json.loads(result)
302 |         assert any(
303 |             expected_symbol_name == s["name_path"].split("/")[-1]
304 |             and expected_kind.lower() in s["kind"].lower()
305 |             and expected_file in s["relative_path"]
306 |             for s in symbols
307 |         ), f"Expected to find {name_path} ({expected_kind}) in {expected_file}. Symbols: {symbols}"
308 | 
309 |     @pytest.mark.parametrize(
310 |         "serena_agent,name_path",
311 |         [
312 |             pytest.param(
313 |                 Language.PYTHON,
314 |                 "/NestedClass",  # Absolute path, NestedClass is not top-level
315 |                 id="absolute_path_non_top_level_no_match",
316 |                 marks=pytest.mark.python,
317 |             ),
318 |             pytest.param(
319 |                 Language.PYTHON,
320 |                 "/NoSuchParent/NestedClass",  # Absolute path with non-existent parent
321 |                 id="absolute_path_non_existent_parent_no_match",
322 |                 marks=pytest.mark.python,
323 |             ),
324 |         ],
325 |         indirect=["serena_agent"],
326 |     )
327 |     def test_find_symbol_name_path_no_match(
328 |         self,
329 |         serena_agent,
330 |         name_path: str,
331 |     ):
332 |         agent = serena_agent
333 | 
334 |         find_symbol_tool = agent.get_tool(FindSymbolTool)
335 |         result = find_symbol_tool.apply_ex(
336 |             name_path_pattern=name_path,
337 |             depth=0,
338 |             substring_matching=True,
339 |         )
340 | 
341 |         symbols = json.loads(result)
342 |         assert not symbols, f"Expected to find no symbols for {name_path}. Symbols found: {symbols}"
343 | 
344 |     @pytest.mark.parametrize(
345 |         "serena_agent,name_path,num_expected",
346 |         [
347 |             pytest.param(
348 |                 Language.JAVA,
349 |                 "Model/getName",
350 |                 2,
351 |                 id="overloaded_java_method",
352 |                 marks=pytest.mark.java,
353 |             ),
354 |         ],
355 |         indirect=["serena_agent"],
356 |     )
357 |     def test_find_symbol_overloaded_function(self, serena_agent: SerenaAgent, name_path: str, num_expected: int):
358 |         """
359 |         Tests whether the FindSymbolTool can find all overloads of a function/method
360 |         (provided that the overload id remains unspecified in the name path)
361 |         """
362 |         agent = serena_agent
363 | 
364 |         find_symbol_tool = agent.get_tool(FindSymbolTool)
365 |         result = find_symbol_tool.apply_ex(
366 |             name_path_pattern=name_path,
367 |             depth=0,
368 |             substring_matching=False,
369 |         )
370 | 
371 |         symbols = json.loads(result)
372 |         assert (
373 |             len(symbols) == num_expected
374 |         ), f"Expected to find {num_expected} symbols for overloaded function {name_path}. Symbols found: {symbols}"
375 | 
376 |     @pytest.mark.parametrize(
377 |         "serena_agent,name_path,relative_path",
378 |         [
379 |             pytest.param(
380 |                 Language.JAVA,
381 |                 "Model/getName",
382 |                 os.path.join("src", "main", "java", "test_repo", "Model.java"),
383 |                 id="overloaded_java_method",
384 |                 marks=pytest.mark.java,
385 |             ),
386 |         ],
387 |         indirect=["serena_agent"],
388 |     )
389 |     def test_non_unique_symbol_reference_error(self, serena_agent: SerenaAgent, name_path: str, relative_path: str):
390 |         """
391 |         Tests whether the tools operating on a well-defined symbol raises an error when the symbol reference is non-unique.
392 |         We exemplarily test a retrieval tool (FindReferencingSymbolsTool) and an editing tool (ReplaceSymbolBodyTool).
393 |         """
394 |         match_text = "multiple"
395 | 
396 |         find_refs_tool = serena_agent.get_tool(FindReferencingSymbolsTool)
397 |         with pytest.raises(ValueError, match=match_text):
398 |             find_refs_tool.apply(name_path=name_path, relative_path=relative_path)
399 | 
400 |         replace_symbol_body_tool = serena_agent.get_tool(ReplaceSymbolBodyTool)
401 |         with pytest.raises(ValueError, match=match_text):
402 |             replace_symbol_body_tool.apply(name_path=name_path, relative_path=relative_path, body="")
403 | 
404 |     @pytest.mark.parametrize(
405 |         "serena_agent",
406 |         [
407 |             pytest.param(
408 |                 Language.TYPESCRIPT,
409 |                 marks=pytest.mark.typescript,
410 |             ),
411 |         ],
412 |         indirect=["serena_agent"],
413 |     )
414 |     def test_replace_content_regex_with_wildcard_ok(self, serena_agent: SerenaAgent):
415 |         """
416 |         Tests a regex-based content replacement that has a unique match
417 |         """
418 |         relative_path = "ws_manager.js"
419 |         with project_file_modification_context(serena_agent, relative_path):
420 |             replace_content_tool = serena_agent.get_tool(ReplaceContentTool)
421 |             result = replace_content_tool.apply(
422 |                 needle=r'catch \(error\) \{\s*console.error\("Failed to connect.*?\}',
423 |                 repl='catch(error) { console.log("Never mind"); }',
424 |                 relative_path=relative_path,
425 |                 mode="regex",
426 |             )
427 |             assert result == SUCCESS_RESULT
428 | 
429 |     @pytest.mark.parametrize(
430 |         "serena_agent",
431 |         [
432 |             pytest.param(
433 |                 Language.TYPESCRIPT,
434 |                 marks=pytest.mark.typescript,
435 |             ),
436 |         ],
437 |         indirect=["serena_agent"],
438 |     )
439 |     @pytest.mark.parametrize("mode", ["literal", "regex"])
440 |     def test_replace_content_with_backslashes(self, serena_agent: SerenaAgent, mode: Literal["literal", "regex"]):
441 |         """
442 |         Tests a content replacement where the needle and replacement strings contain backslashes.
443 |         This is a regression test for escaping issues.
444 |         """
445 |         relative_path = "ws_manager.js"
446 |         needle = r'console.log("WebSocketManager initializing\nStatus OK");'
447 |         repl = r'console.log("WebSocketManager initialized\nAll systems go!");'
448 |         replace_content_tool = serena_agent.get_tool(ReplaceContentTool)
449 |         mode: Literal["literal", "regex"]
450 |         with project_file_modification_context(serena_agent, relative_path):
451 |             result = replace_content_tool.apply(
452 |                 needle=re.escape(needle) if mode == "regex" else needle,
453 |                 repl=repl,
454 |                 relative_path=relative_path,
455 |                 mode=mode,
456 |             )
457 |             assert result == SUCCESS_RESULT
458 |             new_content = read_project_file(serena_agent.get_active_project(), relative_path)
459 |             assert repl in new_content
460 | 
461 |     @pytest.mark.parametrize(
462 |         "serena_agent",
463 |         [
464 |             pytest.param(
465 |                 Language.TYPESCRIPT,
466 |                 marks=pytest.mark.typescript,
467 |             ),
468 |         ],
469 |         indirect=["serena_agent"],
470 |     )
471 |     def test_replace_content_regex_with_wildcard_ambiguous(self, serena_agent: SerenaAgent):
472 |         """
473 |         Tests that an ambiguous replacement where there is a larger match that internally contains
474 |         a smaller match triggers an exception
475 |         """
476 |         replace_content_tool = serena_agent.get_tool(ReplaceContentTool)
477 |         with pytest.raises(ValueError, match="ambiguous"):
478 |             replace_content_tool.apply(
479 |                 needle=r'catch \(error\) \{.*?this\.updateConnectionStatus\("Connection failed", false\);.*?\}',
480 |                 repl='catch(error) { console.log("Never mind"); }',
481 |                 relative_path="ws_manager.js",
482 |                 mode="regex",
483 |             )
484 | 
```

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

```python
  1 | import json
  2 | import logging
  3 | import os
  4 | from abc import ABC, abstractmethod
  5 | from collections.abc import Iterable, Iterator, Reversible
  6 | from contextlib import contextmanager
  7 | from typing import TYPE_CHECKING, Generic, Optional, TypeVar, cast
  8 | 
  9 | from serena.symbol import JetBrainsSymbol, LanguageServerSymbol, LanguageServerSymbolRetriever, PositionInFile, Symbol
 10 | from solidlsp import SolidLanguageServer, ls_types
 11 | from solidlsp.ls import LSPFileBuffer
 12 | from solidlsp.ls_utils import PathUtils, TextUtils
 13 | 
 14 | from .constants import DEFAULT_SOURCE_FILE_ENCODING
 15 | from .project import Project
 16 | from .tools.jetbrains_plugin_client import JetBrainsPluginClient
 17 | 
 18 | if TYPE_CHECKING:
 19 |     from .agent import SerenaAgent
 20 | 
 21 | 
 22 | log = logging.getLogger(__name__)
 23 | TSymbol = TypeVar("TSymbol", bound=Symbol)
 24 | 
 25 | 
 26 | class CodeEditor(Generic[TSymbol], ABC):
 27 |     def __init__(self, project_root: str, agent: Optional["SerenaAgent"] = None) -> None:
 28 |         self.project_root = project_root
 29 |         self.agent = agent
 30 | 
 31 |         # set encoding based on active project, if available
 32 |         encoding = DEFAULT_SOURCE_FILE_ENCODING
 33 |         if agent is not None:
 34 |             project = agent.get_active_project()
 35 |             if project is not None:
 36 |                 encoding = project.project_config.encoding
 37 |         self.encoding = encoding
 38 | 
 39 |     class EditedFile(ABC):
 40 |         def __init__(self, relative_path: str) -> None:
 41 |             self.relative_path = relative_path
 42 | 
 43 |         @abstractmethod
 44 |         def get_contents(self) -> str:
 45 |             """
 46 |             :return: the contents of the file.
 47 |             """
 48 | 
 49 |         @abstractmethod
 50 |         def set_contents(self, contents: str) -> None:
 51 |             """
 52 |             Fully resets the contents of the file.
 53 | 
 54 |             :param contents: the new contents
 55 |             """
 56 | 
 57 |         @abstractmethod
 58 |         def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
 59 |             pass
 60 | 
 61 |         @abstractmethod
 62 |         def insert_text_at_position(self, pos: PositionInFile, text: str) -> None:
 63 |             pass
 64 | 
 65 |     @contextmanager
 66 |     def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
 67 |         """
 68 |         Context manager for opening a file
 69 |         """
 70 |         raise NotImplementedError("This method must be overridden for each subclass")
 71 | 
 72 |     @contextmanager
 73 |     def edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
 74 |         """
 75 |         Context manager for editing a file.
 76 |         """
 77 |         with self._open_file_context(relative_path) as edited_file:
 78 |             yield edited_file
 79 |             # save the file
 80 |             self._save_edited_file(edited_file)
 81 | 
 82 |     def _save_edited_file(self, edited_file: "CodeEditor.EditedFile") -> None:
 83 |         abs_path = os.path.join(self.project_root, edited_file.relative_path)
 84 |         with open(abs_path, "w", encoding=self.encoding) as f:
 85 |             f.write(edited_file.get_contents())
 86 | 
 87 |     @abstractmethod
 88 |     def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> TSymbol:
 89 |         """
 90 |         Finds the unique symbol with the given name in the given file.
 91 |         If no such symbol exists, raises a ValueError.
 92 | 
 93 |         :param name_path: the name path
 94 |         :param relative_file_path: the relative path of the file in which to search for the symbol.
 95 |         :return: the unique symbol
 96 |         """
 97 | 
 98 |     def replace_body(self, name_path: str, relative_file_path: str, body: str) -> None:
 99 |         """
100 |         Replaces the body of the symbol with the given name_path in the given file.
101 | 
102 |         :param name_path: the name path of the symbol to replace.
103 |         :param relative_file_path: the relative path of the file in which the symbol is defined.
104 |         :param body: the new body
105 |         """
106 |         symbol = self._find_unique_symbol(name_path, relative_file_path)
107 |         start_pos = symbol.get_body_start_position_or_raise()
108 |         end_pos = symbol.get_body_end_position_or_raise()
109 | 
110 |         with self.edited_file_context(relative_file_path) as edited_file:
111 |             # make sure the replacement adds no additional newlines (before or after) - all newlines
112 |             # and whitespace before/after should remain the same, so we strip it entirely
113 |             body = body.strip()
114 | 
115 |             edited_file.delete_text_between_positions(start_pos, end_pos)
116 |             edited_file.insert_text_at_position(start_pos, body)
117 | 
118 |     @staticmethod
119 |     def _count_leading_newlines(text: Iterable) -> int:
120 |         cnt = 0
121 |         for c in text:
122 |             if c == "\n":
123 |                 cnt += 1
124 |             elif c == "\r":
125 |                 continue
126 |             else:
127 |                 break
128 |         return cnt
129 | 
130 |     @classmethod
131 |     def _count_trailing_newlines(cls, text: Reversible) -> int:
132 |         return cls._count_leading_newlines(reversed(text))
133 | 
134 |     def insert_after_symbol(self, name_path: str, relative_file_path: str, body: str) -> None:
135 |         """
136 |         Inserts content after the symbol with the given name in the given file.
137 |         """
138 |         symbol = self._find_unique_symbol(name_path, relative_file_path)
139 | 
140 |         # make sure body always ends with at least one newline
141 |         if not body.endswith("\n"):
142 |             body += "\n"
143 | 
144 |         pos = symbol.get_body_end_position_or_raise()
145 | 
146 |         # start at the beginning of the next line
147 |         col = 0
148 |         line = pos.line + 1
149 | 
150 |         # make sure a suitable number of leading empty lines is used (at least 0/1 depending on the symbol type,
151 |         # otherwise as many as the caller wanted to insert)
152 |         original_leading_newlines = self._count_leading_newlines(body)
153 |         body = body.lstrip("\r\n")
154 |         min_empty_lines = 0
155 |         if symbol.is_neighbouring_definition_separated_by_empty_line():
156 |             min_empty_lines = 1
157 |         num_leading_empty_lines = max(min_empty_lines, original_leading_newlines)
158 |         if num_leading_empty_lines:
159 |             body = ("\n" * num_leading_empty_lines) + body
160 | 
161 |         # make sure the one line break succeeding the original symbol, which we repurposed as prefix via
162 |         # `line += 1`, is replaced
163 |         body = body.rstrip("\r\n") + "\n"
164 | 
165 |         with self.edited_file_context(relative_file_path) as edited_file:
166 |             edited_file.insert_text_at_position(PositionInFile(line, col), body)
167 | 
168 |     def insert_before_symbol(self, name_path: str, relative_file_path: str, body: str) -> None:
169 |         """
170 |         Inserts content before the symbol with the given name in the given file.
171 |         """
172 |         symbol = self._find_unique_symbol(name_path, relative_file_path)
173 |         symbol_start_pos = symbol.get_body_start_position_or_raise()
174 | 
175 |         # insert position is the start of line where the symbol is defined
176 |         line = symbol_start_pos.line
177 |         col = 0
178 | 
179 |         original_trailing_empty_lines = self._count_trailing_newlines(body) - 1
180 | 
181 |         # ensure eol is present at end
182 |         body = body.rstrip() + "\n"
183 | 
184 |         # add suitable number of trailing empty lines after the body (at least 0/1 depending on the symbol type,
185 |         # otherwise as many as the caller wanted to insert)
186 |         min_trailing_empty_lines = 0
187 |         if symbol.is_neighbouring_definition_separated_by_empty_line():
188 |             min_trailing_empty_lines = 1
189 |         num_trailing_newlines = max(min_trailing_empty_lines, original_trailing_empty_lines)
190 |         body += "\n" * num_trailing_newlines
191 | 
192 |         # apply edit
193 |         with self.edited_file_context(relative_file_path) as edited_file:
194 |             edited_file.insert_text_at_position(PositionInFile(line=line, col=col), body)
195 | 
196 |     def insert_at_line(self, relative_path: str, line: int, content: str) -> None:
197 |         """
198 |         Inserts content at the given line in the given file.
199 | 
200 |         :param relative_path: the relative path of the file in which to insert content
201 |         :param line: the 0-based index of the line to insert content at
202 |         :param content: the content to insert
203 |         """
204 |         with self.edited_file_context(relative_path) as edited_file:
205 |             edited_file.insert_text_at_position(PositionInFile(line, 0), content)
206 | 
207 |     def delete_lines(self, relative_path: str, start_line: int, end_line: int) -> None:
208 |         """
209 |         Deletes lines in the given file.
210 | 
211 |         :param relative_path: the relative path of the file in which to delete lines
212 |         :param start_line: the 0-based index of the first line to delete (inclusive)
213 |         :param end_line: the 0-based index of the last line to delete (inclusive)
214 |         """
215 |         start_col = 0
216 |         end_line_for_delete = end_line + 1
217 |         end_col = 0
218 |         with self.edited_file_context(relative_path) as edited_file:
219 |             start_pos = PositionInFile(line=start_line, col=start_col)
220 |             end_pos = PositionInFile(line=end_line_for_delete, col=end_col)
221 |             edited_file.delete_text_between_positions(start_pos, end_pos)
222 | 
223 |     def delete_symbol(self, name_path: str, relative_file_path: str) -> None:
224 |         """
225 |         Deletes the symbol with the given name in the given file.
226 |         """
227 |         symbol = self._find_unique_symbol(name_path, relative_file_path)
228 |         start_pos = symbol.get_body_start_position_or_raise()
229 |         end_pos = symbol.get_body_end_position_or_raise()
230 |         with self.edited_file_context(relative_file_path) as edited_file:
231 |             edited_file.delete_text_between_positions(start_pos, end_pos)
232 | 
233 |     @abstractmethod
234 |     def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str:
235 |         """
236 |         Renames the symbol with the given name throughout the codebase.
237 | 
238 |         :param name_path: the name path of the symbol to rename
239 |         :param relative_file_path: the relative path of the file containing the symbol
240 |         :param new_name: the new name for the symbol
241 |         :return: a status message
242 |         """
243 | 
244 | 
245 | class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]):
246 |     def __init__(self, symbol_retriever: LanguageServerSymbolRetriever, agent: Optional["SerenaAgent"] = None):
247 |         super().__init__(project_root=symbol_retriever.get_root_path(), agent=agent)
248 |         self._symbol_retriever = symbol_retriever
249 | 
250 |     def _get_language_server(self, relative_path: str) -> SolidLanguageServer:
251 |         return self._symbol_retriever.get_language_server(relative_path)
252 | 
253 |     class EditedFile(CodeEditor.EditedFile):
254 |         def __init__(self, lang_server: SolidLanguageServer, relative_path: str, file_buffer: LSPFileBuffer):
255 |             super().__init__(relative_path)
256 |             self._lang_server = lang_server
257 |             self._file_buffer = file_buffer
258 | 
259 |         def get_contents(self) -> str:
260 |             return self._file_buffer.contents
261 | 
262 |         def set_contents(self, contents: str) -> None:
263 |             self._file_buffer.contents = contents
264 | 
265 |         def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
266 |             self._lang_server.delete_text_between_positions(self.relative_path, start_pos.to_lsp_position(), end_pos.to_lsp_position())
267 | 
268 |         def insert_text_at_position(self, pos: PositionInFile, text: str) -> None:
269 |             self._lang_server.insert_text_at_position(self.relative_path, pos.line, pos.col, text)
270 | 
271 |         def apply_text_edits(self, text_edits: list[ls_types.TextEdit]) -> None:
272 |             return self._lang_server.apply_text_edits_to_file(self.relative_path, text_edits)
273 | 
274 |     @contextmanager
275 |     def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
276 |         lang_server = self._get_language_server(relative_path)
277 |         with lang_server.open_file(relative_path) as file_buffer:
278 |             yield self.EditedFile(lang_server, relative_path, file_buffer)
279 | 
280 |     def _get_code_file_content(self, relative_path: str) -> str:
281 |         """Get the content of a file using the language server."""
282 |         lang_server = self._get_language_server(relative_path)
283 |         return lang_server.language_server.retrieve_full_file_content(relative_path)
284 | 
285 |     def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> LanguageServerSymbol:
286 |         return self._symbol_retriever.find_unique(name_path, within_relative_path=relative_file_path)
287 | 
288 |     def _relative_path_from_uri(self, uri: str) -> str:
289 |         return os.path.relpath(PathUtils.uri_to_path(uri), self.project_root)
290 | 
291 |     class EditOperation(ABC):
292 |         @abstractmethod
293 |         def apply(self) -> None:
294 |             pass
295 | 
296 |     class EditOperationFileTextEdits(EditOperation):
297 |         def __init__(self, code_editor: "LanguageServerCodeEditor", file_uri: str, text_edits: list[ls_types.TextEdit]):
298 |             self._code_editor = code_editor
299 |             self._relative_path = code_editor._relative_path_from_uri(file_uri)
300 |             self._text_edits = text_edits
301 | 
302 |         def apply(self) -> None:
303 |             with self._code_editor.edited_file_context(self._relative_path) as edited_file:
304 |                 edited_file = cast(LanguageServerCodeEditor.EditedFile, edited_file)
305 |                 edited_file.apply_text_edits(self._text_edits)
306 | 
307 |     class EditOperationRenameFile(EditOperation):
308 |         def __init__(self, code_editor: "LanguageServerCodeEditor", old_uri: str, new_uri: str):
309 |             self._code_editor = code_editor
310 |             self._old_relative_path = code_editor._relative_path_from_uri(old_uri)
311 |             self._new_relative_path = code_editor._relative_path_from_uri(new_uri)
312 | 
313 |         def apply(self) -> None:
314 |             old_abs_path = os.path.join(self._code_editor.project_root, self._old_relative_path)
315 |             new_abs_path = os.path.join(self._code_editor.project_root, self._new_relative_path)
316 |             os.rename(old_abs_path, new_abs_path)
317 | 
318 |     def _workspace_edit_to_edit_operations(self, workspace_edit: ls_types.WorkspaceEdit) -> list["LanguageServerCodeEditor.EditOperation"]:
319 |         operations: list[LanguageServerCodeEditor.EditOperation] = []
320 | 
321 |         if "changes" in workspace_edit:
322 |             for uri, edits in workspace_edit["changes"].items():
323 |                 operations.append(self.EditOperationFileTextEdits(self, uri, edits))
324 | 
325 |         if "documentChanges" in workspace_edit:
326 |             for change in workspace_edit["documentChanges"]:
327 |                 if "textDocument" in change and "edits" in change:
328 |                     operations.append(self.EditOperationFileTextEdits(self, change["textDocument"]["uri"], change["edits"]))
329 |                 elif "kind" in change:
330 |                     if change["kind"] == "rename":
331 |                         operations.append(self.EditOperationRenameFile(self, change["oldUri"], change["newUri"]))
332 |                     else:
333 |                         raise ValueError(f"Unhandled document change kind: {change}; Please report to Serena developers.")
334 |                 else:
335 |                     raise ValueError(f"Unhandled document change format: {change}; Please report to Serena developers.")
336 | 
337 |         return operations
338 | 
339 |     def _apply_workspace_edit(self, workspace_edit: ls_types.WorkspaceEdit) -> int:
340 |         """
341 |         Applies a WorkspaceEdit
342 | 
343 |         :param workspace_edit: the edit to apply
344 |         :return: number of edit operations applied
345 |         """
346 |         operations = self._workspace_edit_to_edit_operations(workspace_edit)
347 |         for operation in operations:
348 |             operation.apply()
349 |         return len(operations)
350 | 
351 |     def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str:
352 |         symbol = self._find_unique_symbol(name_path, relative_file_path)
353 |         if not symbol.location.has_position_in_file():
354 |             raise ValueError(f"Symbol '{name_path}' does not have a valid position in file for renaming")
355 | 
356 |         # After has_position_in_file check, line and column are guaranteed to be non-None
357 |         assert symbol.location.line is not None
358 |         assert symbol.location.column is not None
359 | 
360 |         lang_server = self._get_language_server(relative_file_path)
361 |         rename_result = lang_server.request_rename_symbol_edit(
362 |             relative_file_path=relative_file_path, line=symbol.location.line, column=symbol.location.column, new_name=new_name
363 |         )
364 |         if rename_result is None:
365 |             raise ValueError(
366 |                 f"Language server for {lang_server.language_id} returned no rename edits for symbol '{name_path}'. "
367 |                 f"The symbol might not support renaming."
368 |             )
369 |         num_changes = self._apply_workspace_edit(rename_result)
370 | 
371 |         if num_changes == 0:
372 |             raise ValueError(
373 |                 f"Renaming symbol '{name_path}' to '{new_name}' resulted in no changes being applied; renaming may not be supported."
374 |             )
375 | 
376 |         msg = f"Successfully renamed '{name_path}' to '{new_name}' ({num_changes} changes applied)"
377 |         return msg
378 | 
379 | 
380 | class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]):
381 |     def __init__(self, project: Project, agent: Optional["SerenaAgent"] = None) -> None:
382 |         self._project = project
383 |         super().__init__(project_root=project.project_root, agent=agent)
384 | 
385 |     class EditedFile(CodeEditor.EditedFile):
386 |         def __init__(self, relative_path: str, project: Project):
387 |             super().__init__(relative_path)
388 |             path = os.path.join(project.project_root, relative_path)
389 |             log.info("Editing file: %s", path)
390 |             with open(path, encoding=project.project_config.encoding) as f:
391 |                 self._content = f.read()
392 | 
393 |         def get_contents(self) -> str:
394 |             return self._content
395 | 
396 |         def set_contents(self, contents: str) -> None:
397 |             self._content = contents
398 | 
399 |         def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None:
400 |             self._content, _ = TextUtils.delete_text_between_positions(
401 |                 self._content, start_pos.line, start_pos.col, end_pos.line, end_pos.col
402 |             )
403 | 
404 |         def insert_text_at_position(self, pos: PositionInFile, text: str) -> None:
405 |             self._content, _, _ = TextUtils.insert_text_at_position(self._content, pos.line, pos.col, text)
406 | 
407 |     @contextmanager
408 |     def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
409 |         yield self.EditedFile(relative_path, self._project)
410 | 
411 |     def _save_edited_file(self, edited_file: "CodeEditor.EditedFile") -> None:
412 |         super()._save_edited_file(edited_file)
413 |         with JetBrainsPluginClient.from_project(self._project) as client:
414 |             client.refresh_file(edited_file.relative_path)
415 | 
416 |     def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> JetBrainsSymbol:
417 |         with JetBrainsPluginClient.from_project(self._project) as client:
418 |             result = client.find_symbol(name_path, relative_path=relative_file_path, include_body=False, depth=0, include_location=True)
419 |             symbols = result["symbols"]
420 |             if not symbols:
421 |                 raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}")
422 |             if len(symbols) > 1:
423 |                 raise ValueError(
424 |                     f"Found multiple {len(symbols)} symbols with name {name_path} in file {relative_file_path}: "
425 |                     + json.dumps(symbols, indent=2)
426 |                 )
427 |             return JetBrainsSymbol(symbols[0], self._project)
428 | 
429 |     def rename_symbol(self, name_path: str, relative_file_path: str, new_name: str) -> str:
430 |         with JetBrainsPluginClient.from_project(self._project) as client:
431 |             client.rename_symbol(
432 |                 name_path=name_path,
433 |                 relative_path=relative_file_path,
434 |                 new_name=new_name,
435 |                 rename_in_comments=False,
436 |                 rename_in_text_occurrences=False,
437 |             )
438 |             return "Success"
439 | 
```

--------------------------------------------------------------------------------
/.github/workflows/pytest.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Tests on CI
  2 | 
  3 | on:
  4 |   pull_request:
  5 |   push:
  6 |     branches:
  7 |       - main
  8 | 
  9 | concurrency:
 10 |   group: ci-${{ github.workflow }}-${{ github.ref }}
 11 |   cancel-in-progress: true
 12 | 
 13 | jobs:
 14 |   cpu:
 15 |     name: Tests on ${{ matrix.os }}
 16 |     runs-on: ${{ matrix.os }}
 17 |     strategy:
 18 |       fail-fast: false
 19 |       matrix:
 20 |         os: [ubuntu-latest, windows-latest, macos-latest]
 21 |         python-version: ["3.11"]
 22 |     steps:
 23 |       - uses: actions/checkout@v3
 24 |       - name: Free disk space
 25 |         if: runner.os == 'Linux'
 26 |         run: |
 27 |           df -h
 28 |           sudo rm -rf /usr/local/lib/android
 29 |           sudo rm -rf /usr/share/dotnet
 30 |           sudo rm -rf /opt/ghc
 31 |           sudo rm -rf /opt/hostedtoolcache
 32 |           sudo apt-get clean
 33 |           sudo apt-get autoremove -y
 34 |           docker system prune -af || true
 35 |           df -h
 36 |       - name: Set up Python ${{ matrix.python-version }}
 37 |         uses: actions/setup-python@v4
 38 |         with:
 39 |           python-version: "${{ matrix.python-version }}"
 40 |       - uses: actions/setup-go@v5
 41 |         with:
 42 |           go-version: ">=1.17.0"
 43 |       - name: Set up Node.js
 44 |         uses: actions/setup-node@v4
 45 |         with:
 46 |           node-version: '20.x'
 47 |       - name: Ensure cached directory exist before calling cache-related actions
 48 |         shell: bash
 49 |         run: |
 50 |           mkdir -p $HOME/.serena/language_servers/static
 51 |           mkdir -p $HOME/.cache/go-build
 52 |           mkdir -p $HOME/go/bin
 53 |       - name: Install uv
 54 |         shell: bash
 55 |         run: curl -LsSf https://astral.sh/uv/install.sh | sh
 56 |       - name: Cache uv virtualenv
 57 |         id: cache-uv
 58 |         uses: actions/cache@v3
 59 |         with:
 60 |           path: .venv
 61 |           key: uv-venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
 62 |       - name: Create virtual environment
 63 |         shell: bash
 64 |         run: |
 65 |           if [ ! -d ".venv" ]; then
 66 |             uv venv
 67 |           fi
 68 |       - name: Install dependencies
 69 |         shell: bash
 70 |         run: uv pip install -e ".[dev]"
 71 |       - name: Check formatting
 72 |         shell: bash
 73 |         run: uv run poe lint
 74 |       # Add Go bin directory to PATH for this workflow
 75 |       # GITHUB_PATH is a special file that GitHub Actions uses to modify PATH
 76 |       # Writing to this file adds the directory to the PATH for subsequent steps
 77 |       - name: Cache Go binaries
 78 |         id: cache-go-binaries
 79 |         uses: actions/cache@v3
 80 |         with:
 81 |           path: |
 82 |             ~/go/bin
 83 |             ~/.cache/go-build
 84 |           key: go-binaries-${{ runner.os }}-gopls-latest
 85 |       - name: Install gopls
 86 |         if: steps.cache-go-binaries.outputs.cache-hit != 'true'
 87 |         shell: bash
 88 |         run: go install golang.org/x/tools/gopls@latest
 89 |       - name: Set up Elixir
 90 |         if: runner.os != 'Windows'
 91 |         uses: erlef/setup-beam@v1
 92 |         with:
 93 |           elixir-version: "1.19.3"
 94 |           otp-version: "28"
 95 | #      Erlang currently not tested in CI, random hangings on macos, always hangs on ubuntu
 96 | #      In local tests, erlang seems to work though
 97 | #      - name: Install Erlang Language Server
 98 | #        if: runner.os != 'Windows'
 99 | #        shell: bash
100 | #        run: |
101 | #          # Install rebar3 if not already available
102 | #          which rebar3 || (curl -fsSL https://github.com/erlang/rebar3/releases/download/3.23.0/rebar3 -o /tmp/rebar3 && chmod +x /tmp/rebar3 && sudo mv /tmp/rebar3 /usr/local/bin/rebar3)
103 | #          # Clone and build erlang_ls
104 | #          git clone https://github.com/erlang-ls/erlang_ls.git /tmp/erlang_ls
105 | #          cd /tmp/erlang_ls
106 | #          make install PREFIX=/usr/local
107 | #          # Ensure erlang_ls is in PATH
108 | #          echo "$HOME/.local/bin" >> $GITHUB_PATH
109 |       - name: Install clojure tools
110 |         uses: DeLaGuardo/[email protected]
111 |         with:
112 |           cli: latest
113 |       - name: Setup Java (for JVM based languages)
114 |         uses: actions/setup-java@v4
115 |         with:
116 |           distribution: 'temurin'
117 |           java-version: '17'
118 |       - name: Setup .NET SDK (for F# and C# languages)
119 |         uses: actions/setup-dotnet@v4
120 |         with:
121 |           dotnet-version: '8.0.x'
122 |       - name: Install Terraform
123 |         uses: hashicorp/setup-terraform@v3
124 |         with:
125 |           terraform_version: "1.5.0"
126 |           terraform_wrapper: false
127 |       # - name: Install swift
128 |       #   if: runner.os != 'Windows'
129 |       #   uses: swift-actions/setup-swift@v2
130 |       # Installation of swift with the action screws with installation of ruby on macOS for some reason
131 |       # We can try again when version 3 of the action is released, where they will also use swiftly
132 |       # Until then, we use custom code to install swift. Sourcekit-lsp is installed automatically with swift
133 |       - name: Install Swift with swiftly (macOS)
134 |         if: runner.os == 'macOS'
135 |         run: |
136 |           echo "=== Installing swiftly on macOS ==="
137 |           curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && \
138 |           installer -pkg swiftly.pkg -target CurrentUserHomeDirectory && \
139 |           ~/.swiftly/bin/swiftly init --quiet-shell-followup && \
140 |           . "${SWIFTLY_HOME_DIR:-$HOME/.swiftly}/env.sh" && \
141 |           hash -r
142 |           swiftly install --use 6.1.2
143 |           swiftly use 6.1.2
144 |           echo "~/.swiftly/bin" >> $GITHUB_PATH
145 |           echo "Swiftly installed successfully"
146 |           # Verify sourcekit-lsp is working before proceeding
147 |           echo "=== Verifying sourcekit-lsp installation ==="
148 |           which sourcekit-lsp || echo "Warning: sourcekit-lsp not found in PATH"
149 |           sourcekit-lsp --help || echo "Warning: sourcekit-lsp not responding"
150 |       - name: Install Swift with swiftly (Ubuntu)
151 |         if: runner.os == 'Linux'
152 |         run: |
153 |           echo "=== Installing swiftly on Ubuntu ==="
154 |           # Install dependencies BEFORE Swift to avoid exit code 1
155 |           sudo apt-get update
156 |           sudo apt-get -y install libcurl4-openssl-dev
157 |           curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \
158 |           tar zxf swiftly-$(uname -m).tar.gz && \
159 |           ./swiftly init --quiet-shell-followup && \
160 |           . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \
161 |           hash -r
162 |           swiftly install --use 6.1.2
163 |           swiftly use 6.1.2
164 |           echo "=== Adding Swift toolchain to PATH ==="
165 |           echo "$HOME/.local/share/swiftly/bin" >> $GITHUB_PATH
166 |           echo "Swiftly installed successfully!"
167 |           # Verify sourcekit-lsp is working before proceeding
168 |           echo "=== Verifying sourcekit-lsp installation ==="
169 |           which sourcekit-lsp || echo "Warning: sourcekit-lsp not found in PATH"
170 |           sourcekit-lsp --help || echo "Warning: sourcekit-lsp not responding"
171 |       - name: Install Ruby
172 |         uses: ruby/setup-ruby@v1
173 |         with:
174 |           ruby-version: '3.4'
175 |       - name: Install Ruby language server
176 |         shell: bash
177 |         run: gem install ruby-lsp
178 |       - name: Install R
179 |         uses: r-lib/actions/setup-r@v2
180 |         with:
181 |           r-version: '4.4.2'
182 |           use-public-rspm: true
183 |       - name: Install R language server
184 |         shell: bash
185 |         run: |
186 |           Rscript -e "install.packages('languageserver', repos='https://cloud.r-project.org')"
187 |       - name: Set up Julia
188 |         uses: julia-actions/setup-julia@v2
189 |         with:
190 |           version: '1.10'
191 |       - name: Install Julia LanguageServer
192 |         shell: bash
193 |         run: julia -e 'using Pkg; Pkg.add("LanguageServer")'
194 |       - name: Setup Haskell toolchain
195 |         if: runner.os != 'Windows'
196 |         uses: haskell/ghcup-setup@v1
197 |         with:
198 |           ghc: '9.12.2'
199 |           cabal: '3.10.3.0'
200 |           hls: '2.11.0.0'
201 |       - name: Verify Haskell tools
202 |         if: runner.os != 'Windows'
203 |         run: |
204 |           echo "Verifying installed Haskell tools:"
205 |           which ghc && ghc --version
206 |           which cabal && cabal --version
207 |           # HLS verification - non-blocking in case of version incompatibility
208 |           if command -v haskell-language-server-wrapper &>/dev/null; then
209 |             echo "Found haskell-language-server-wrapper"
210 |             haskell-language-server-wrapper --version || echo "WARNING: HLS wrapper found but version check failed"
211 |           elif command -v haskell-language-server &>/dev/null; then
212 |             echo "Found haskell-language-server"
213 |             haskell-language-server --version || echo "WARNING: HLS found but version check failed"
214 |           else
215 |             echo "WARNING: HLS not found (may be incompatible with GHC 9.12.2)"
216 |             echo "This is not a critical error - tests will use HLS if available at runtime"
217 |           fi
218 |         shell: bash
219 |       - name: Pre-build Haskell test project for HLS
220 |         if: runner.os != 'Windows'
221 |         run: |
222 |           cd test/resources/repos/haskell/test_repo
223 |           cabal update
224 |           cabal build --only-dependencies
225 |           cabal build
226 |           echo "Haskell test project built successfully"
227 |         shell: bash
228 |       - name: Install Zig
229 |         uses: goto-bus-stop/setup-zig@v2
230 |         with:
231 |           version: 0.14.1
232 |       - name: Install ZLS (Zig Language Server)
233 |         shell: bash
234 |         run: |
235 |           if [[ "${{ runner.os }}" == "Linux" ]]; then
236 |             wget https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-linux.tar.xz
237 |             tar -xf zls-x86_64-linux.tar.xz
238 |             sudo mv zls /usr/local/bin/
239 |             rm zls-x86_64-linux.tar.xz
240 |           elif [[ "${{ runner.os }}" == "macOS" ]]; then
241 |             wget https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-macos.tar.xz
242 |             tar -xf zls-x86_64-macos.tar.xz
243 |             sudo mv zls /usr/local/bin/
244 |             rm zls-x86_64-macos.tar.xz
245 |           elif [[ "${{ runner.os }}" == "Windows" ]]; then
246 |             curl -L -o zls.zip https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-windows.zip
247 |             unzip -o zls.zip
248 |             mkdir -p "$HOME/bin"
249 |             mv zls.exe "$HOME/bin/"
250 |             echo "$HOME/bin" >> $GITHUB_PATH
251 |             rm zls.zip
252 |           fi
253 |       - name: Install Lua Language Server
254 |         shell: bash
255 |         run: |
256 |           LUA_LS_VERSION="3.15.0"
257 |           LUA_LS_DIR="$HOME/.serena/language_servers/lua"
258 |           mkdir -p "$LUA_LS_DIR"
259 |           
260 |           if [[ "${{ runner.os }}" == "Linux" ]]; then
261 |             if [[ "$(uname -m)" == "x86_64" ]]; then
262 |               wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-x64.tar.gz
263 |               tar -xzf lua-language-server-${LUA_LS_VERSION}-linux-x64.tar.gz -C "$LUA_LS_DIR"
264 |             else
265 |               wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-arm64.tar.gz
266 |               tar -xzf lua-language-server-${LUA_LS_VERSION}-linux-arm64.tar.gz -C "$LUA_LS_DIR"
267 |             fi
268 |             chmod +x "$LUA_LS_DIR/bin/lua-language-server"
269 |             # Create wrapper script instead of symlink to ensure supporting files are found
270 |             echo '#!/bin/bash' | sudo tee /usr/local/bin/lua-language-server > /dev/null
271 |             echo 'cd "${HOME}/.serena/language_servers/lua/bin"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null
272 |             echo 'exec ./lua-language-server "$@"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null
273 |             sudo chmod +x /usr/local/bin/lua-language-server
274 |             rm lua-language-server-*.tar.gz
275 |           elif [[ "${{ runner.os }}" == "macOS" ]]; then
276 |             if [[ "$(uname -m)" == "x86_64" ]]; then
277 |               wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-darwin-x64.tar.gz
278 |               tar -xzf lua-language-server-${LUA_LS_VERSION}-darwin-x64.tar.gz -C "$LUA_LS_DIR"
279 |             else
280 |               wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-darwin-arm64.tar.gz
281 |               tar -xzf lua-language-server-${LUA_LS_VERSION}-darwin-arm64.tar.gz -C "$LUA_LS_DIR"
282 |             fi
283 |             chmod +x "$LUA_LS_DIR/bin/lua-language-server"
284 |             # Create wrapper script instead of symlink to ensure supporting files are found
285 |             echo '#!/bin/bash' | sudo tee /usr/local/bin/lua-language-server > /dev/null
286 |             echo 'cd "${HOME}/.serena/language_servers/lua/bin"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null
287 |             echo 'exec ./lua-language-server "$@"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null
288 |             sudo chmod +x /usr/local/bin/lua-language-server
289 |             rm lua-language-server-*.tar.gz
290 |           elif [[ "${{ runner.os }}" == "Windows" ]]; then
291 |             curl -L -o lua-ls.zip https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-win32-x64.zip
292 |             unzip -o lua-ls.zip -d "$LUA_LS_DIR"
293 |             # For Windows, we'll add the bin directory directly to PATH
294 |             # The lua-language-server.exe can find its supporting files relative to its location
295 |             echo "$LUA_LS_DIR/bin" >> $GITHUB_PATH
296 |             rm lua-ls.zip
297 |           fi
298 |       - name: Install Perl::LanguageServer
299 |         if: runner.os != 'Windows'
300 |         shell: bash
301 |         run: |
302 |           if [[ "${{ runner.os }}" == "Linux" ]]; then
303 |             sudo apt-get update
304 |             sudo apt-get install -y cpanminus build-essential libanyevent-perl libio-aio-perl
305 |           elif [[ "${{ runner.os }}" == "macOS" ]]; then
306 |             brew install cpanminus
307 |           fi
308 |           PERL_MM_USE_DEFAULT=1 cpanm --notest --force Perl::LanguageServer
309 |           # Set up Perl local::lib environment for subsequent steps
310 |           echo "PERL5LIB=$HOME/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}" >> $GITHUB_ENV
311 |           echo "PERL_LOCAL_LIB_ROOT=$HOME/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}" >> $GITHUB_ENV
312 |           echo "PERL_MB_OPT=--install_base \"$HOME/perl5\"" >> $GITHUB_ENV
313 |           echo "PERL_MM_OPT=INSTALL_BASE=$HOME/perl5" >> $GITHUB_ENV
314 |           echo "$HOME/perl5/bin" >> $GITHUB_PATH
315 |       - name: Install Elm
316 |         shell: bash
317 |         run: npm install -g [email protected]
318 |       - name: Install Nix
319 |         if: runner.os != 'Windows'  # Nix doesn't support Windows natively
320 |         uses: cachix/install-nix-action@v30
321 |         with:
322 |           nix_path: nixpkgs=channel:nixos-unstable
323 |       - name: Install nixd (Nix Language Server)
324 |         if: runner.os != 'Windows'  # Skip on Windows since Nix isn't available
325 |         shell: bash
326 |         run: |
327 |           # Install nixd using nix
328 |           nix profile install github:nix-community/nixd
329 | 
330 |           # Verify nixd is installed and working
331 |           if ! command -v nixd &> /dev/null; then
332 |             echo "nixd installation failed or not in PATH"
333 |             exit 1
334 |           fi
335 | 
336 |           echo "$HOME/.nix-profile/bin" >> $GITHUB_PATH
337 |       - name: Verify Nix package build
338 |         if: runner.os != 'Windows'  # Nix only supported on Linux/macOS
339 |         shell: bash
340 |         run: |
341 |           # Verify the flake builds successfully
342 |           nix build --no-link
343 |       - name: Install Regal (Rego Language Server)
344 |         shell: bash
345 |         run: |
346 |           REGAL_VERSION="0.36.1"
347 | 
348 |           if [[ "${{ runner.os }}" == "Linux" ]]; then
349 |             if [[ "$(uname -m)" == "x86_64" ]]; then
350 |               curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Linux_x86_64
351 |             else
352 |               curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Linux_arm64
353 |             fi
354 |             chmod +x regal
355 |             sudo mv regal /usr/local/bin/
356 |           elif [[ "${{ runner.os }}" == "macOS" ]]; then
357 |             if [[ "$(uname -m)" == "x86_64" ]]; then
358 |               curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Darwin_x86_64
359 |             else
360 |               curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Darwin_arm64
361 |             fi
362 |             chmod +x regal
363 |             sudo mv regal /usr/local/bin/
364 |           elif [[ "${{ runner.os }}" == "Windows" ]]; then
365 |             curl -L -o regal.exe https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Windows_x86_64.exe
366 |             mkdir -p "$HOME/bin"
367 |             mv regal.exe "$HOME/bin/"
368 |             echo "$HOME/bin" >> $GITHUB_PATH
369 |           fi
370 |       - name: Install Free Pascal Compiler
371 |         shell: bash
372 |         run: |
373 |           if [[ "${{ runner.os }}" == "Linux" ]]; then
374 |             sudo apt-get update
375 |             sudo apt-get install -y fpc fpc-source
376 |             # Set environment variables for pasls
377 |             echo "PP=/usr/bin/fpc" >> $GITHUB_ENV
378 |             # Find FPC source directory (version may vary)
379 |             FPCDIR=$(ls -d /usr/share/fpcsrc/*/ 2>/dev/null | head -1 || echo "/usr/share/fpcsrc")
380 |             echo "FPCDIR=$FPCDIR" >> $GITHUB_ENV
381 |           elif [[ "${{ runner.os }}" == "macOS" ]]; then
382 |             brew install fpc
383 |             # Download FPC source from SourceForge (fpc-src-laz cask is incompatible with ARM64)
384 |             FPC_VERSION="3.2.2"
385 |             curl -L -o fpc-source.tar.gz "https://sourceforge.net/projects/freepascal/files/Source/${FPC_VERSION}/fpc-${FPC_VERSION}.source.tar.gz/download"
386 |             mkdir -p "$HOME/fpcsrc"
387 |             tar -xzf fpc-source.tar.gz -C "$HOME/fpcsrc"
388 |             rm fpc-source.tar.gz
389 |             # Set environment variables for pasls
390 |             echo "PP=$(which fpc)" >> $GITHUB_ENV
391 |             echo "FPCDIR=$HOME/fpcsrc/fpc-${FPC_VERSION}" >> $GITHUB_ENV
392 |           elif [[ "${{ runner.os }}" == "Windows" ]]; then
393 |             FPC_VERSION="3.2.2"
394 |             # Download freepascal-ootb (includes FPC compiler)
395 |             curl -L -o fpc-ootb.zip https://github.com/fredvs/freepascal-ootb/releases/download/${FPC_VERSION}/fpc-ootb-322-x86_64-win64.zip
396 |             mkdir -p "$HOME/fpc"
397 |             unzip -q fpc-ootb.zip -d "$HOME/fpc"
398 |             rm fpc-ootb.zip
399 |             # Download FPC source from SourceForge (fpc-ootb only has compiled units, not source)
400 |             curl -L -o fpc-source.zip "https://sourceforge.net/projects/freepascal/files/Source/${FPC_VERSION}/fpc-${FPC_VERSION}.source.zip/download"
401 |             mkdir -p "$HOME/fpcsrc"
402 |             unzip -q fpc-source.zip -d "$HOME/fpcsrc"
403 |             rm fpc-source.zip
404 |             # Find fpc executable (fpc-ootb uses fpc-ootb.exe as the compiler)
405 |             echo "=== FPC directory structure ==="
406 |             find "$HOME/fpc" -name "*.exe" -type f 2>/dev/null | head -10
407 |             FPC_EXE=$(find "$HOME/fpc" -name "fpc-ootb-64.exe" -type f 2>/dev/null | head -1)
408 |             echo "Found FPC executable: $FPC_EXE"
409 |             echo "Found FPC source dir: $HOME/fpcsrc/fpc-${FPC_VERSION}"
410 |             # Set environment variables for pasls
411 |             echo "PP=$FPC_EXE" >> $GITHUB_ENV
412 |             echo "FPCDIR=$HOME/fpcsrc/fpc-${FPC_VERSION}" >> $GITHUB_ENV
413 |             # Add FPC bin directory to PATH
414 |             FPC_BIN_DIR=$(dirname "$FPC_EXE")
415 |             echo "$FPC_BIN_DIR" >> $GITHUB_PATH
416 |           fi
417 |       - name: Verify FPC installation
418 |         shell: bash
419 |         run: |
420 |           echo "PP=$PP"
421 |           echo "FPCDIR=$FPCDIR"
422 |           fpc -v || echo "FPC not in PATH, using PP directly"
423 |       - name: Cache language servers
424 |         id: cache-language-servers
425 |         uses: actions/cache@v3
426 |         with:
427 |           path: ~/.serena/language_servers/static
428 |           key: language-servers-${{ runner.os }}-v1
429 |           restore-keys: |
430 |             language-servers-${{ runner.os }}-
431 |       - name: Report free disk space
432 |         if: runner.os == 'Linux'
433 |         run: |
434 |           echo "Free disk space before tests:"
435 |           df -h
436 |       - name: Test with pytest
437 |         shell: bash
438 |         run: uv run poe test
439 |       - name: Type-checking with mypy
440 |         shell: bash
441 |         run: uv run poe type-check
442 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/ls_request.py:
--------------------------------------------------------------------------------

```python
  1 | from typing import TYPE_CHECKING, Any, Union
  2 | 
  3 | from solidlsp.lsp_protocol_handler import lsp_types
  4 | 
  5 | if TYPE_CHECKING:
  6 |     from .ls_handler import SolidLanguageServerHandler
  7 | 
  8 | 
  9 | class LanguageServerRequest:
 10 |     def __init__(self, handler: "SolidLanguageServerHandler"):
 11 |         self.handler = handler
 12 | 
 13 |     def _send_request(self, method: str, params: Any | None = None) -> Any:
 14 |         return self.handler.send_request(method, params)
 15 | 
 16 |     def implementation(self, params: lsp_types.ImplementationParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]:
 17 |         """A request to resolve the implementation locations of a symbol at a given text
 18 |         document position. The request's parameter is of type [TextDocumentPositionParams]
 19 |         (#TextDocumentPositionParams) the response is of type {@link Definition} or a
 20 |         Thenable that resolves to such.
 21 |         """
 22 |         return self._send_request("textDocument/implementation", params)
 23 | 
 24 |     def type_definition(
 25 |         self, params: lsp_types.TypeDefinitionParams
 26 |     ) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]:
 27 |         """A request to resolve the type definition locations of a symbol at a given text
 28 |         document position. The request's parameter is of type [TextDocumentPositionParams]
 29 |         (#TextDocumentPositionParams) the response is of type {@link Definition} or a
 30 |         Thenable that resolves to such.
 31 |         """
 32 |         return self._send_request("textDocument/typeDefinition", params)
 33 | 
 34 |     def document_color(self, params: lsp_types.DocumentColorParams) -> list["lsp_types.ColorInformation"]:
 35 |         """A request to list all color symbols found in a given text document. The request's
 36 |         parameter is of type {@link DocumentColorParams} the
 37 |         response is of type {@link ColorInformation ColorInformation[]} or a Thenable
 38 |         that resolves to such.
 39 |         """
 40 |         return self._send_request("textDocument/documentColor", params)
 41 | 
 42 |     def color_presentation(self, params: lsp_types.ColorPresentationParams) -> list["lsp_types.ColorPresentation"]:
 43 |         """A request to list all presentation for a color. The request's
 44 |         parameter is of type {@link ColorPresentationParams} the
 45 |         response is of type {@link ColorInformation ColorInformation[]} or a Thenable
 46 |         that resolves to such.
 47 |         """
 48 |         return self._send_request("textDocument/colorPresentation", params)
 49 | 
 50 |     def folding_range(self, params: lsp_types.FoldingRangeParams) -> list["lsp_types.FoldingRange"] | None:
 51 |         """A request to provide folding ranges in a document. The request's
 52 |         parameter is of type {@link FoldingRangeParams}, the
 53 |         response is of type {@link FoldingRangeList} or a Thenable
 54 |         that resolves to such.
 55 |         """
 56 |         return self._send_request("textDocument/foldingRange", params)
 57 | 
 58 |     def declaration(self, params: lsp_types.DeclarationParams) -> Union["lsp_types.Declaration", list["lsp_types.LocationLink"], None]:
 59 |         """A request to resolve the type definition locations of a symbol at a given text
 60 |         document position. The request's parameter is of type [TextDocumentPositionParams]
 61 |         (#TextDocumentPositionParams) the response is of type {@link Declaration}
 62 |         or a typed array of {@link DeclarationLink} or a Thenable that resolves
 63 |         to such.
 64 |         """
 65 |         return self._send_request("textDocument/declaration", params)
 66 | 
 67 |     def selection_range(self, params: lsp_types.SelectionRangeParams) -> list["lsp_types.SelectionRange"] | None:
 68 |         """A request to provide selection ranges in a document. The request's
 69 |         parameter is of type {@link SelectionRangeParams}, the
 70 |         response is of type {@link SelectionRange SelectionRange[]} or a Thenable
 71 |         that resolves to such.
 72 |         """
 73 |         return self._send_request("textDocument/selectionRange", params)
 74 | 
 75 |     def prepare_call_hierarchy(self, params: lsp_types.CallHierarchyPrepareParams) -> list["lsp_types.CallHierarchyItem"] | None:
 76 |         """A request to result a `CallHierarchyItem` in a document at a given position.
 77 |         Can be used as an input to an incoming or outgoing call hierarchy.
 78 | 
 79 |         @since 3.16.0
 80 |         """
 81 |         return self._send_request("textDocument/prepareCallHierarchy", params)
 82 | 
 83 |     def incoming_calls(self, params: lsp_types.CallHierarchyIncomingCallsParams) -> list["lsp_types.CallHierarchyIncomingCall"] | None:
 84 |         """A request to resolve the incoming calls for a given `CallHierarchyItem`.
 85 | 
 86 |         @since 3.16.0
 87 |         """
 88 |         return self._send_request("callHierarchy/incomingCalls", params)
 89 | 
 90 |     def outgoing_calls(self, params: lsp_types.CallHierarchyOutgoingCallsParams) -> list["lsp_types.CallHierarchyOutgoingCall"] | None:
 91 |         """A request to resolve the outgoing calls for a given `CallHierarchyItem`.
 92 | 
 93 |         @since 3.16.0
 94 |         """
 95 |         return self._send_request("callHierarchy/outgoingCalls", params)
 96 | 
 97 |     def semantic_tokens_full(self, params: lsp_types.SemanticTokensParams) -> Union["lsp_types.SemanticTokens", None]:
 98 |         """@since 3.16.0"""
 99 |         return self._send_request("textDocument/semanticTokens/full", params)
100 | 
101 |     def semantic_tokens_delta(
102 |         self, params: lsp_types.SemanticTokensDeltaParams
103 |     ) -> Union["lsp_types.SemanticTokens", "lsp_types.SemanticTokensDelta", None]:
104 |         """@since 3.16.0"""
105 |         return self._send_request("textDocument/semanticTokens/full/delta", params)
106 | 
107 |     def semantic_tokens_range(self, params: lsp_types.SemanticTokensRangeParams) -> Union["lsp_types.SemanticTokens", None]:
108 |         """@since 3.16.0"""
109 |         return self._send_request("textDocument/semanticTokens/range", params)
110 | 
111 |     def linked_editing_range(self, params: lsp_types.LinkedEditingRangeParams) -> Union["lsp_types.LinkedEditingRanges", None]:
112 |         """A request to provide ranges that can be edited together.
113 | 
114 |         @since 3.16.0
115 |         """
116 |         return self._send_request("textDocument/linkedEditingRange", params)
117 | 
118 |     def will_create_files(self, params: lsp_types.CreateFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
119 |         """The will create files request is sent from the client to the server before files are actually
120 |         created as long as the creation is triggered from within the client.
121 | 
122 |         @since 3.16.0
123 |         """
124 |         return self._send_request("workspace/willCreateFiles", params)
125 | 
126 |     def will_rename_files(self, params: lsp_types.RenameFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
127 |         """The will rename files request is sent from the client to the server before files are actually
128 |         renamed as long as the rename is triggered from within the client.
129 | 
130 |         @since 3.16.0
131 |         """
132 |         return self._send_request("workspace/willRenameFiles", params)
133 | 
134 |     def will_delete_files(self, params: lsp_types.DeleteFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
135 |         """The did delete files notification is sent from the client to the server when
136 |         files were deleted from within the client.
137 | 
138 |         @since 3.16.0
139 |         """
140 |         return self._send_request("workspace/willDeleteFiles", params)
141 | 
142 |     def moniker(self, params: lsp_types.MonikerParams) -> list["lsp_types.Moniker"] | None:
143 |         """A request to get the moniker of a symbol at a given text document position.
144 |         The request parameter is of type {@link TextDocumentPositionParams}.
145 |         The response is of type {@link Moniker Moniker[]} or `null`.
146 |         """
147 |         return self._send_request("textDocument/moniker", params)
148 | 
149 |     def prepare_type_hierarchy(self, params: lsp_types.TypeHierarchyPrepareParams) -> list["lsp_types.TypeHierarchyItem"] | None:
150 |         """A request to result a `TypeHierarchyItem` in a document at a given position.
151 |         Can be used as an input to a subtypes or supertypes type hierarchy.
152 | 
153 |         @since 3.17.0
154 |         """
155 |         return self._send_request("textDocument/prepareTypeHierarchy", params)
156 | 
157 |     def type_hierarchy_supertypes(self, params: lsp_types.TypeHierarchySupertypesParams) -> list["lsp_types.TypeHierarchyItem"] | None:
158 |         """A request to resolve the supertypes for a given `TypeHierarchyItem`.
159 | 
160 |         @since 3.17.0
161 |         """
162 |         return self._send_request("typeHierarchy/supertypes", params)
163 | 
164 |     def type_hierarchy_subtypes(self, params: lsp_types.TypeHierarchySubtypesParams) -> list["lsp_types.TypeHierarchyItem"] | None:
165 |         """A request to resolve the subtypes for a given `TypeHierarchyItem`.
166 | 
167 |         @since 3.17.0
168 |         """
169 |         return self._send_request("typeHierarchy/subtypes", params)
170 | 
171 |     def inline_value(self, params: lsp_types.InlineValueParams) -> list["lsp_types.InlineValue"] | None:
172 |         """A request to provide inline values in a document. The request's parameter is of
173 |         type {@link InlineValueParams}, the response is of type
174 |         {@link InlineValue InlineValue[]} or a Thenable that resolves to such.
175 | 
176 |         @since 3.17.0
177 |         """
178 |         return self._send_request("textDocument/inlineValue", params)
179 | 
180 |     def inlay_hint(self, params: lsp_types.InlayHintParams) -> list["lsp_types.InlayHint"] | None:
181 |         """A request to provide inlay hints in a document. The request's parameter is of
182 |         type {@link InlayHintsParams}, the response is of type
183 |         {@link InlayHint InlayHint[]} or a Thenable that resolves to such.
184 | 
185 |         @since 3.17.0
186 |         """
187 |         return self._send_request("textDocument/inlayHint", params)
188 | 
189 |     def resolve_inlay_hint(self, params: lsp_types.InlayHint) -> "lsp_types.InlayHint":
190 |         """A request to resolve additional properties for an inlay hint.
191 |         The request's parameter is of type {@link InlayHint}, the response is
192 |         of type {@link InlayHint} or a Thenable that resolves to such.
193 | 
194 |         @since 3.17.0
195 |         """
196 |         return self._send_request("inlayHint/resolve", params)
197 | 
198 |     def text_document_diagnostic(self, params: lsp_types.DocumentDiagnosticParams) -> "lsp_types.DocumentDiagnosticReport":
199 |         """The document diagnostic request definition.
200 | 
201 |         @since 3.17.0
202 |         """
203 |         return self._send_request("textDocument/diagnostic", params)
204 | 
205 |     def workspace_diagnostic(self, params: lsp_types.WorkspaceDiagnosticParams) -> "lsp_types.WorkspaceDiagnosticReport":
206 |         """The workspace diagnostic request definition.
207 | 
208 |         @since 3.17.0
209 |         """
210 |         return self._send_request("workspace/diagnostic", params)
211 | 
212 |     def initialize(self, params: lsp_types.InitializeParams) -> "lsp_types.InitializeResult":
213 |         """The initialize request is sent from the client to the server.
214 |         It is sent once as the request after starting up the server.
215 |         The requests parameter is of type {@link InitializeParams}
216 |         the response if of type {@link InitializeResult} of a Thenable that
217 |         resolves to such.
218 |         """
219 |         return self._send_request("initialize", params)
220 | 
221 |     def shutdown(self) -> None:
222 |         """A shutdown request is sent from the client to the server.
223 |         It is sent once when the client decides to shutdown the
224 |         server. The only notification that is sent after a shutdown request
225 |         is the exit event.
226 |         """
227 |         return self._send_request("shutdown")
228 | 
229 |     def will_save_wait_until(self, params: lsp_types.WillSaveTextDocumentParams) -> list["lsp_types.TextEdit"] | None:
230 |         """A document will save request is sent from the client to the server before
231 |         the document is actually saved. The request can return an array of TextEdits
232 |         which will be applied to the text document before it is saved. Please note that
233 |         clients might drop results if computing the text edits took too long or if a
234 |         server constantly fails on this request. This is done to keep the save fast and
235 |         reliable.
236 |         """
237 |         return self._send_request("textDocument/willSaveWaitUntil", params)
238 | 
239 |     def completion(self, params: lsp_types.CompletionParams) -> Union[list["lsp_types.CompletionItem"], "lsp_types.CompletionList", None]:
240 |         """Request to request completion at a given text document position. The request's
241 |         parameter is of type {@link TextDocumentPosition} the response
242 |         is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList}
243 |         or a Thenable that resolves to such.
244 | 
245 |         The request can delay the computation of the {@link CompletionItem.detail `detail`}
246 |         and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve`
247 |         request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
248 |         `filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
249 |         """
250 |         return self._send_request("textDocument/completion", params)
251 | 
252 |     def resolve_completion_item(self, params: lsp_types.CompletionItem) -> "lsp_types.CompletionItem":
253 |         """Request to resolve additional information for a given completion item.The request's
254 |         parameter is of type {@link CompletionItem} the response
255 |         is of type {@link CompletionItem} or a Thenable that resolves to such.
256 |         """
257 |         return self._send_request("completionItem/resolve", params)
258 | 
259 |     def hover(self, params: lsp_types.HoverParams) -> Union["lsp_types.Hover", None]:
260 |         """Request to request hover information at a given text document position. The request's
261 |         parameter is of type {@link TextDocumentPosition} the response is of
262 |         type {@link Hover} or a Thenable that resolves to such.
263 |         """
264 |         return self._send_request("textDocument/hover", params)
265 | 
266 |     def signature_help(self, params: lsp_types.SignatureHelpParams) -> Union["lsp_types.SignatureHelp", None]:
267 |         return self._send_request("textDocument/signatureHelp", params)
268 | 
269 |     def definition(self, params: lsp_types.DefinitionParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]:
270 |         """A request to resolve the definition location of a symbol at a given text
271 |         document position. The request's parameter is of type [TextDocumentPosition]
272 |         (#TextDocumentPosition) the response is of either type {@link Definition}
273 |         or a typed array of {@link DefinitionLink} or a Thenable that resolves
274 |         to such.
275 |         """
276 |         return self._send_request("textDocument/definition", params)
277 | 
278 |     def references(self, params: lsp_types.ReferenceParams) -> list["lsp_types.Location"] | None:
279 |         """A request to resolve project-wide references for the symbol denoted
280 |         by the given text document position. The request's parameter is of
281 |         type {@link ReferenceParams} the response is of type
282 |         {@link Location Location[]} or a Thenable that resolves to such.
283 |         """
284 |         return self._send_request("textDocument/references", params)
285 | 
286 |     def document_highlight(self, params: lsp_types.DocumentHighlightParams) -> list["lsp_types.DocumentHighlight"] | None:
287 |         """Request to resolve a {@link DocumentHighlight} for a given
288 |         text document position. The request's parameter is of type [TextDocumentPosition]
289 |         (#TextDocumentPosition) the request response is of type [DocumentHighlight[]]
290 |         (#DocumentHighlight) or a Thenable that resolves to such.
291 |         """
292 |         return self._send_request("textDocument/documentHighlight", params)
293 | 
294 |     def document_symbol(
295 |         self, params: lsp_types.DocumentSymbolParams
296 |     ) -> list["lsp_types.SymbolInformation"] | list["lsp_types.DocumentSymbol"] | None:
297 |         """A request to list all symbols found in a given text document. The request's
298 |         parameter is of type {@link TextDocumentIdentifier} the
299 |         response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
300 |         that resolves to such.
301 |         """
302 |         return self._send_request("textDocument/documentSymbol", params)
303 | 
304 |     def code_action(self, params: lsp_types.CodeActionParams) -> list[Union["lsp_types.Command", "lsp_types.CodeAction"]] | None:
305 |         """A request to provide commands for the given text document and range."""
306 |         return self._send_request("textDocument/codeAction", params)
307 | 
308 |     def resolve_code_action(self, params: lsp_types.CodeAction) -> "lsp_types.CodeAction":
309 |         """Request to resolve additional information for a given code action.The request's
310 |         parameter is of type {@link CodeAction} the response
311 |         is of type {@link CodeAction} or a Thenable that resolves to such.
312 |         """
313 |         return self._send_request("codeAction/resolve", params)
314 | 
315 |     def workspace_symbol(
316 |         self, params: lsp_types.WorkspaceSymbolParams
317 |     ) -> list["lsp_types.SymbolInformation"] | list["lsp_types.WorkspaceSymbol"] | None:
318 |         """A request to list project-wide symbols matching the query string given
319 |         by the {@link WorkspaceSymbolParams}. The response is
320 |         of type {@link SymbolInformation SymbolInformation[]} or a Thenable that
321 |         resolves to such.
322 | 
323 |         @since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients
324 |          need to advertise support for WorkspaceSymbols via the client capability
325 |          `workspace.symbol.resolveSupport`.
326 |         """
327 |         return self._send_request("workspace/symbol", params)
328 | 
329 |     def resolve_workspace_symbol(self, params: lsp_types.WorkspaceSymbol) -> "lsp_types.WorkspaceSymbol":
330 |         """A request to resolve the range inside the workspace
331 |         symbol's location.
332 | 
333 |         @since 3.17.0
334 |         """
335 |         return self._send_request("workspaceSymbol/resolve", params)
336 | 
337 |     def code_lens(self, params: lsp_types.CodeLensParams) -> list["lsp_types.CodeLens"] | None:
338 |         """A request to provide code lens for the given text document."""
339 |         return self._send_request("textDocument/codeLens", params)
340 | 
341 |     def resolve_code_lens(self, params: lsp_types.CodeLens) -> "lsp_types.CodeLens":
342 |         """A request to resolve a command for a given code lens."""
343 |         return self._send_request("codeLens/resolve", params)
344 | 
345 |     def document_link(self, params: lsp_types.DocumentLinkParams) -> list["lsp_types.DocumentLink"] | None:
346 |         """A request to provide document links"""
347 |         return self._send_request("textDocument/documentLink", params)
348 | 
349 |     def resolve_document_link(self, params: lsp_types.DocumentLink) -> "lsp_types.DocumentLink":
350 |         """Request to resolve additional information for a given document link. The request's
351 |         parameter is of type {@link DocumentLink} the response
352 |         is of type {@link DocumentLink} or a Thenable that resolves to such.
353 |         """
354 |         return self._send_request("documentLink/resolve", params)
355 | 
356 |     def formatting(self, params: lsp_types.DocumentFormattingParams) -> list["lsp_types.TextEdit"] | None:
357 |         """A request to to format a whole document."""
358 |         return self._send_request("textDocument/formatting", params)
359 | 
360 |     def range_formatting(self, params: lsp_types.DocumentRangeFormattingParams) -> list["lsp_types.TextEdit"] | None:
361 |         """A request to to format a range in a document."""
362 |         return self._send_request("textDocument/rangeFormatting", params)
363 | 
364 |     def on_type_formatting(self, params: lsp_types.DocumentOnTypeFormattingParams) -> list["lsp_types.TextEdit"] | None:
365 |         """A request to format a document on type."""
366 |         return self._send_request("textDocument/onTypeFormatting", params)
367 | 
368 |     def rename(self, params: lsp_types.RenameParams) -> Union["lsp_types.WorkspaceEdit", None]:
369 |         """A request to rename a symbol."""
370 |         return self._send_request("textDocument/rename", params)
371 | 
372 |     def prepare_rename(self, params: lsp_types.PrepareRenameParams) -> Union["lsp_types.PrepareRenameResult", None]:
373 |         """A request to test and perform the setup necessary for a rename.
374 | 
375 |         @since 3.16 - support for default behavior
376 |         """
377 |         return self._send_request("textDocument/prepareRename", params)
378 | 
379 |     def execute_command(self, params: lsp_types.ExecuteCommandParams) -> Union["lsp_types.LSPAny", None]:
380 |         """A request send from the client to the server to execute a command. The request might return
381 |         a workspace edit which the client will apply to the workspace.
382 |         """
383 |         return self._send_request("workspace/executeCommand", params)
384 | 
```

--------------------------------------------------------------------------------
/test/solidlsp/dart/test_dart_basic.py:
--------------------------------------------------------------------------------

```python
  1 | import os
  2 | from pathlib import Path
  3 | 
  4 | import pytest
  5 | 
  6 | from solidlsp import SolidLanguageServer
  7 | from solidlsp.ls_config import Language
  8 | from solidlsp.ls_types import SymbolKind
  9 | from solidlsp.ls_utils import SymbolUtils
 10 | 
 11 | 
 12 | @pytest.mark.dart
 13 | class TestDartLanguageServer:
 14 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
 15 |     @pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
 16 |     def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 17 |         """Test that the language server starts and stops successfully."""
 18 |         # The fixture already handles start and stop
 19 |         assert language_server.is_running()
 20 |         assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
 21 | 
 22 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
 23 |     @pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
 24 |     def test_find_definition_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 25 |         """Test finding definition of a method within the same file."""
 26 |         # In lib/main.dart:
 27 |         # Line 105: final result1 = calc.add(5, 3); // Reference to add method
 28 |         # Line 12: int add(int a, int b) {        // Definition of add method
 29 |         # Find definition of 'add' method from its usage
 30 |         main_dart_path = str(repo_path / "lib" / "main.dart")
 31 | 
 32 |         # Position: calc.add(5, 3) - cursor on 'add'
 33 |         # Line 105 (1-indexed) = line 104 (0-indexed), char position around 22
 34 |         definition_location_list = language_server.request_definition(main_dart_path, 104, 22)
 35 | 
 36 |         assert definition_location_list, f"Expected non-empty definition_location_list but got {definition_location_list=}"
 37 |         assert len(definition_location_list) >= 1
 38 |         definition_location = definition_location_list[0]
 39 |         assert definition_location["uri"].endswith("main.dart")
 40 |         # Definition of add method should be around line 11 (0-indexed)
 41 |         # But language server may return different positions
 42 |         assert definition_location["range"]["start"]["line"] >= 0
 43 | 
 44 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
 45 |     @pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
 46 |     def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 47 |         """Test finding definition across different files."""
 48 |         # Test finding definition of MathHelper class which is in helper.dart
 49 |         # In lib/main.dart line 50: MathHelper.power(step1, 2)
 50 |         main_dart_path = str(repo_path / "lib" / "main.dart")
 51 | 
 52 |         # Position: MathHelper.power(step1, 2) - cursor on 'MathHelper'
 53 |         # Line 50 (1-indexed) = line 49 (0-indexed), char position around 18
 54 |         definition_location_list = language_server.request_definition(main_dart_path, 49, 18)
 55 | 
 56 |         # Skip the test if language server doesn't find cross-file references
 57 |         # This is acceptable for a basic test - the important thing is that LS is working
 58 |         if not definition_location_list:
 59 |             pytest.skip("Language server doesn't support cross-file definition lookup for this case")
 60 | 
 61 |         assert len(definition_location_list) >= 1
 62 |         definition_location = definition_location_list[0]
 63 |         assert definition_location["uri"].endswith("helper.dart")
 64 |         assert definition_location["range"]["start"]["line"] >= 0
 65 | 
 66 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
 67 |     @pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
 68 |     def test_find_definition_class_method(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 69 |         """Test finding definition of a class method."""
 70 |         # In lib/main.dart:
 71 |         # Line 50: final step2 = MathHelper.power(step1, 2); // Reference to MathHelper.power method
 72 |         # In lib/helper.dart:
 73 |         # Line 14: static double power(double base, int exponent) { // Definition of power method
 74 |         main_dart_path = str(repo_path / "lib" / "main.dart")
 75 | 
 76 |         # Position: MathHelper.power(step1, 2) - cursor on 'power'
 77 |         # Line 50 (1-indexed) = line 49 (0-indexed), char position around 30
 78 |         definition_location_list = language_server.request_definition(main_dart_path, 49, 30)
 79 | 
 80 |         assert definition_location_list, f"Expected non-empty definition_location_list but got {definition_location_list=}"
 81 |         assert len(definition_location_list) >= 1
 82 |         definition_location = definition_location_list[0]
 83 |         assert definition_location["uri"].endswith("helper.dart")
 84 |         # Definition of power method should be around line 13 (0-indexed)
 85 |         assert 12 <= definition_location["range"]["start"]["line"] <= 16
 86 | 
 87 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
 88 |     @pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
 89 |     def test_find_references_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
 90 |         """Test finding references to a method within the same file."""
 91 |         main_dart_path = str(repo_path / "lib" / "main.dart")
 92 | 
 93 |         # Find references to the 'add' method from its definition
 94 |         # Line 12: int add(int a, int b) { // Definition of add method
 95 |         # Line 105: final result1 = calc.add(5, 3); // Usage of add method
 96 |         references = language_server.request_references(main_dart_path, 11, 6)  # cursor on 'add' in definition
 97 | 
 98 |         assert references, f"Expected non-empty references but got {references=}"
 99 |         # Should find at least the usage of add method
100 |         assert len(references) >= 1
101 | 
102 |         # Check that we have a reference in main.dart
103 |         main_dart_references = [ref for ref in references if ref["uri"].endswith("main.dart")]
104 |         assert len(main_dart_references) >= 1
105 | 
106 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
107 |     @pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
108 |     def test_find_references_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
109 |         """Test finding references across different files."""
110 |         helper_dart_path = str(repo_path / "lib" / "helper.dart")
111 | 
112 |         # Find references to the 'subtract' function from its definition in helper.dart
113 |         # Definition is in helper.dart, usage is in main.dart
114 |         references = language_server.request_references(helper_dart_path, 4, 4)  # cursor on 'subtract' in definition
115 | 
116 |         assert references, f"Expected non-empty references for subtract function but got {references=}"
117 | 
118 |         # Should find references in main.dart
119 |         main_dart_references = [ref for ref in references if ref["uri"].endswith("main.dart")]
120 |         assert len(main_dart_references) >= 1
121 | 
122 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
123 |     @pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
124 |     def test_find_definition_constructor(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
125 |         """Test finding definition of a constructor call."""
126 |         main_dart_path = str(repo_path / "lib" / "main.dart")
127 | 
128 |         # In lib/main.dart:
129 |         # Line 104: final calc = Calculator(); // Reference to Calculator constructor
130 |         # Line 4: class Calculator {          // Definition of Calculator class
131 |         definition_location_list = language_server.request_definition(main_dart_path, 103, 18)  # cursor on 'Calculator'
132 | 
133 |         assert definition_location_list, f"Expected non-empty definition_location_list but got {definition_location_list=}"
134 |         assert len(definition_location_list) >= 1
135 |         definition_location = definition_location_list[0]
136 |         assert definition_location["uri"].endswith("main.dart")
137 |         # Definition of Calculator class should be around line 3 (0-indexed)
138 |         assert 3 <= definition_location["range"]["start"]["line"] <= 7
139 | 
140 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
141 |     @pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
142 |     def test_find_definition_import(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
143 |         """Test finding definition through imports."""
144 |         models_dart_path = str(repo_path / "lib" / "models.dart")
145 | 
146 |         # Test finding definition of User class name where it's used
147 |         # In lib/models.dart line 27 (constructor): User(this.id, this.name, this.email, this._age);
148 |         definition_location_list = language_server.request_definition(models_dart_path, 26, 2)  # cursor on 'User' in constructor
149 | 
150 |         # Skip if language server doesn't find definition in this case
151 |         if not definition_location_list:
152 |             pytest.skip("Language server doesn't support definition lookup for this case")
153 | 
154 |         assert len(definition_location_list) >= 1
155 |         definition_location = definition_location_list[0]
156 |         # Language server might return SDK files instead of local files
157 |         # This is acceptable behavior - the important thing is that it found a definition
158 |         assert "dart" in definition_location["uri"].lower()
159 | 
160 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
161 |     def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
162 |         """Test finding symbols in the full symbol tree."""
163 |         symbols = language_server.request_full_symbol_tree()
164 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator class not found in symbol tree"
165 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add method not found in symbol tree"
166 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "subtract"), "subtract function not found in symbol tree"
167 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "MathHelper"), "MathHelper class not found in symbol tree"
168 |         assert SymbolUtils.symbol_tree_contains_name(symbols, "User"), "User class not found in symbol tree"
169 | 
170 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
171 |     def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
172 |         """Test finding references using symbol selection range."""
173 |         file_path = os.path.join("lib", "main.dart")
174 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
175 | 
176 |         # Handle nested symbol structure - symbols can be nested in lists
177 |         symbol_list = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
178 | 
179 |         # Find the 'add' method symbol in Calculator class
180 |         add_symbol = None
181 |         for sym in symbol_list:
182 |             if sym.get("name") == "add":
183 |                 add_symbol = sym
184 |                 break
185 |             # Check for nested symbols (methods inside classes)
186 |             if "children" in sym and sym.get("name") == "Calculator":
187 |                 for child in sym["children"]:
188 |                     if child.get("name") == "add":
189 |                         add_symbol = child
190 |                         break
191 |                 if add_symbol:
192 |                     break
193 | 
194 |         assert add_symbol is not None, "Could not find 'add' method symbol in main.dart"
195 |         sel_start = add_symbol["selectionRange"]["start"]
196 |         refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
197 | 
198 |         # Check that we found references - at least one should be in main.dart
199 |         assert any(
200 |             "main.dart" in ref.get("relativePath", "") or "main.dart" in ref.get("uri", "") for ref in refs
201 |         ), "main.dart should reference add method (tried all positions in selectionRange)"
202 | 
203 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
204 |     def test_request_containing_symbol_method(self, language_server: SolidLanguageServer) -> None:
205 |         """Test request_containing_symbol for a method."""
206 |         file_path = os.path.join("lib", "main.dart")
207 |         # Line 14 is inside the add method body (around 'final result = a + b;')
208 |         containing_symbol = language_server.request_containing_symbol(file_path, 13, 10, include_body=True)
209 | 
210 |         # Verify that we found the containing symbol
211 |         if containing_symbol is not None:
212 |             assert containing_symbol["name"] == "add"
213 |             assert containing_symbol["kind"] == SymbolKind.Method
214 |             if "body" in containing_symbol:
215 |                 assert "add" in containing_symbol["body"] or "final result" in containing_symbol["body"]
216 | 
217 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
218 |     def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None:
219 |         """Test request_containing_symbol for a class."""
220 |         file_path = os.path.join("lib", "main.dart")
221 |         # Line 4 is the Calculator class definition line
222 |         containing_symbol = language_server.request_containing_symbol(file_path, 4, 6)
223 | 
224 |         # Verify that we found the containing symbol
225 |         if containing_symbol is not None:
226 |             assert containing_symbol["name"] == "Calculator"
227 |             assert containing_symbol["kind"] == SymbolKind.Class
228 | 
229 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
230 |     def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
231 |         """Test request_containing_symbol with nested scopes."""
232 |         file_path = os.path.join("lib", "main.dart")
233 |         # Line 14 is inside the add method inside Calculator class
234 |         containing_symbol = language_server.request_containing_symbol(file_path, 13, 20)
235 | 
236 |         # Verify that we found the innermost containing symbol (the method)
237 |         if containing_symbol is not None:
238 |             assert containing_symbol["name"] == "add"
239 |             assert containing_symbol["kind"] == SymbolKind.Method
240 | 
241 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
242 |     def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None:
243 |         """Test request_defining_symbol for a variable usage."""
244 |         file_path = os.path.join("lib", "main.dart")
245 |         # Line 14 contains 'final result = a + b;' - test position on 'result'
246 |         defining_symbol = language_server.request_defining_symbol(file_path, 13, 10)
247 | 
248 |         # The defining symbol might be the variable itself or the containing method
249 |         # This is acceptable behavior - different language servers handle this differently
250 |         if defining_symbol is not None:
251 |             assert defining_symbol.get("name") in ["result", "add"]
252 |             if defining_symbol.get("name") == "add":
253 |                 assert defining_symbol.get("kind") == SymbolKind.Method.value
254 | 
255 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
256 |     def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None:
257 |         """Test request_defining_symbol for an imported class/function."""
258 |         file_path = os.path.join("lib", "main.dart")
259 |         # Line 20 references 'subtract' which was imported from helper.dart
260 |         defining_symbol = language_server.request_defining_symbol(file_path, 19, 18)
261 | 
262 |         # Verify that we found the defining symbol - this should be the subtract function from helper.dart
263 |         if defining_symbol is not None:
264 |             assert defining_symbol.get("name") == "subtract"
265 |             # Could be Function or Method depending on language server interpretation
266 |             assert defining_symbol.get("kind") in [SymbolKind.Function.value, SymbolKind.Method.value]
267 | 
268 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
269 |     def test_request_defining_symbol_class_method(self, language_server: SolidLanguageServer) -> None:
270 |         """Test request_defining_symbol for a static class method."""
271 |         file_path = os.path.join("lib", "main.dart")
272 |         # Line 50 references MathHelper.power - test position on 'power'
273 |         defining_symbol = language_server.request_defining_symbol(file_path, 49, 30)
274 | 
275 |         # Verify that we found the defining symbol - should be the power method
276 |         if defining_symbol is not None:
277 |             assert defining_symbol.get("name") == "power"
278 |             assert defining_symbol.get("kind") == SymbolKind.Method.value
279 | 
280 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
281 |     def test_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
282 |         """Test getting document symbols from a Dart file."""
283 |         file_path = os.path.join("lib", "main.dart")
284 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
285 | 
286 |         # Check that we have symbols
287 |         assert len(symbols) > 0
288 | 
289 |         # Flatten the symbols if they're nested
290 |         symbol_list = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
291 | 
292 |         # Look for expected classes and methods
293 |         symbol_names = [s.get("name") for s in symbol_list]
294 |         assert "Calculator" in symbol_names
295 | 
296 |         # Check for nested symbols (methods inside classes) - optional
297 |         calculator_symbol = next((s for s in symbol_list if s.get("name") == "Calculator"), None)
298 |         if calculator_symbol and "children" in calculator_symbol and calculator_symbol["children"]:
299 |             method_names = [child.get("name") for child in calculator_symbol["children"]]
300 |             # If children are populated, we should find the add method
301 |             assert "add" in method_names
302 |         else:
303 |             # Some language servers may not populate children in document symbols
304 |             # This is acceptable behavior - the important thing is we found the class
305 |             pass
306 | 
307 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
308 |     def test_request_referencing_symbols_comprehensive(self, language_server: SolidLanguageServer) -> None:
309 |         """Test comprehensive referencing symbols functionality."""
310 |         file_path = os.path.join("lib", "main.dart")
311 |         symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
312 | 
313 |         # Handle nested symbol structure
314 |         symbol_list = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
315 | 
316 |         # Find Calculator class and test its references
317 |         calculator_symbol = None
318 |         for sym in symbol_list:
319 |             if sym.get("name") == "Calculator":
320 |                 calculator_symbol = sym
321 |                 break
322 | 
323 |         if calculator_symbol and "selectionRange" in calculator_symbol:
324 |             sel_start = calculator_symbol["selectionRange"]["start"]
325 |             refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
326 | 
327 |             # Should find references to Calculator (constructor calls, etc.)
328 |             if refs:
329 |                 # Verify the structure of referencing symbols
330 |                 for ref in refs:
331 |                     assert "uri" in ref or "relativePath" in ref
332 |                     if "range" in ref:
333 |                         assert "start" in ref["range"]
334 |                         assert "end" in ref["range"]
335 | 
336 |     @pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
337 |     def test_cross_file_symbol_resolution(self, language_server: SolidLanguageServer) -> None:
338 |         """Test symbol resolution across multiple files."""
339 |         helper_file_path = os.path.join("lib", "helper.dart")
340 | 
341 |         # Test finding references to subtract function from helper.dart in main.dart
342 |         helper_symbols = language_server.request_document_symbols(helper_file_path).get_all_symbols_and_roots()
343 |         symbol_list = helper_symbols[0] if helper_symbols and isinstance(helper_symbols[0], list) else helper_symbols
344 | 
345 |         subtract_symbol = next((s for s in symbol_list if s.get("name") == "subtract"), None)
346 | 
347 |         if subtract_symbol and "selectionRange" in subtract_symbol:
348 |             sel_start = subtract_symbol["selectionRange"]["start"]
349 |             refs = language_server.request_references(helper_file_path, sel_start["line"], sel_start["character"])
350 | 
351 |             # Should find references in main.dart
352 |             main_dart_refs = [ref for ref in refs if "main.dart" in ref.get("uri", "") or "main.dart" in ref.get("relativePath", "")]
353 |             # Note: This may not always work depending on language server capabilities
354 |             # So we don't assert - just verify the structure if we get results
355 |             if main_dart_refs:
356 |                 for ref in main_dart_refs:
357 |                     assert "range" in ref or "location" in ref
358 | 
```

--------------------------------------------------------------------------------
/src/solidlsp/language_servers/omnisharp/initialize_params.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |     "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
  3 |     "processId": "os.getpid()",
  4 |     "clientInfo": {
  5 |         "name": "Visual Studio Code - Insiders",
  6 |         "version": "1.82.0-insider"
  7 |     },
  8 |     "locale": "en",
  9 |     "rootPath": "$rootPath",
 10 |     "rootUri": "$rootUri",
 11 |     "capabilities": {
 12 |         "workspace": {
 13 |             "applyEdit": true,
 14 |             "workspaceEdit": {
 15 |                 "documentChanges": true,
 16 |                 "resourceOperations": [
 17 |                     "create",
 18 |                     "rename",
 19 |                     "delete"
 20 |                 ],
 21 |                 "failureHandling": "textOnlyTransactional",
 22 |                 "normalizesLineEndings": true,
 23 |                 "changeAnnotationSupport": {
 24 |                     "groupsOnLabel": true
 25 |                 }
 26 |             },
 27 |             "configuration": false,
 28 |             "didChangeWatchedFiles": {
 29 |                 "dynamicRegistration": true,
 30 |                 "relativePatternSupport": true
 31 |             },
 32 |             "symbol": {
 33 |                 "dynamicRegistration": true,
 34 |                 "symbolKind": {
 35 |                     "valueSet": [
 36 |                         1,
 37 |                         2,
 38 |                         3,
 39 |                         4,
 40 |                         5,
 41 |                         6,
 42 |                         7,
 43 |                         8,
 44 |                         9,
 45 |                         10,
 46 |                         11,
 47 |                         12,
 48 |                         13,
 49 |                         14,
 50 |                         15,
 51 |                         16,
 52 |                         17,
 53 |                         18,
 54 |                         19,
 55 |                         20,
 56 |                         21,
 57 |                         22,
 58 |                         23,
 59 |                         24,
 60 |                         25,
 61 |                         26
 62 |                     ]
 63 |                 },
 64 |                 "tagSupport": {
 65 |                     "valueSet": [
 66 |                         1
 67 |                     ]
 68 |                 },
 69 |                 "resolveSupport": {
 70 |                     "properties": [
 71 |                         "location.range"
 72 |                     ]
 73 |                 }
 74 |             },
 75 |             "codeLens": {
 76 |                 "refreshSupport": true
 77 |             },
 78 |             "executeCommand": {
 79 |                 "dynamicRegistration": true
 80 |             },
 81 |             "didChangeConfiguration": {
 82 |                 "dynamicRegistration": true
 83 |             },
 84 |             "workspaceFolders": true,
 85 |             "semanticTokens": {
 86 |                 "refreshSupport": true
 87 |             },
 88 |             "fileOperations": {
 89 |                 "dynamicRegistration": true,
 90 |                 "didCreate": true,
 91 |                 "didRename": true,
 92 |                 "didDelete": true,
 93 |                 "willCreate": true,
 94 |                 "willRename": true,
 95 |                 "willDelete": true
 96 |             },
 97 |             "inlineValue": {
 98 |                 "refreshSupport": true
 99 |             },
100 |             "inlayHint": {
101 |                 "refreshSupport": true
102 |             },
103 |             "diagnostics": {
104 |                 "refreshSupport": true
105 |             }
106 |         },
107 |         "textDocument": {
108 |             "publishDiagnostics": {
109 |                 "relatedInformation": true,
110 |                 "versionSupport": false,
111 |                 "tagSupport": {
112 |                     "valueSet": [
113 |                         1,
114 |                         2
115 |                     ]
116 |                 },
117 |                 "codeDescriptionSupport": true,
118 |                 "dataSupport": true
119 |             },
120 |             "synchronization": {
121 |                 "dynamicRegistration": true,
122 |                 "willSave": true,
123 |                 "willSaveWaitUntil": true,
124 |                 "didSave": true
125 |             },
126 |             "completion": {
127 |                 "dynamicRegistration": true,
128 |                 "contextSupport": true,
129 |                 "completionItem": {
130 |                     "snippetSupport": true,
131 |                     "commitCharactersSupport": true,
132 |                     "documentationFormat": [
133 |                         "markdown",
134 |                         "plaintext"
135 |                     ],
136 |                     "deprecatedSupport": true,
137 |                     "preselectSupport": true,
138 |                     "tagSupport": {
139 |                         "valueSet": [
140 |                             1
141 |                         ]
142 |                     },
143 |                     "insertReplaceSupport": true,
144 |                     "resolveSupport": {
145 |                         "properties": [
146 |                             "documentation",
147 |                             "detail",
148 |                             "additionalTextEdits"
149 |                         ]
150 |                     },
151 |                     "insertTextModeSupport": {
152 |                         "valueSet": [
153 |                             1,
154 |                             2
155 |                         ]
156 |                     },
157 |                     "labelDetailsSupport": true
158 |                 },
159 |                 "insertTextMode": 2,
160 |                 "completionItemKind": {
161 |                     "valueSet": [
162 |                         1,
163 |                         2,
164 |                         3,
165 |                         4,
166 |                         5,
167 |                         6,
168 |                         7,
169 |                         8,
170 |                         9,
171 |                         10,
172 |                         11,
173 |                         12,
174 |                         13,
175 |                         14,
176 |                         16,
177 |                         17,
178 |                         18,
179 |                         19,
180 |                         20,
181 |                         21,
182 |                         22,
183 |                         23,
184 |                         24,
185 |                         25
186 |                     ]
187 |                 },
188 |                 "completionList": {
189 |                     "itemDefaults": [
190 |                         "commitCharacters",
191 |                         "editRange",
192 |                         "insertTextFormat",
193 |                         "insertTextMode"
194 |                     ]
195 |                 }
196 |             },
197 |             "hover": {
198 |                 "dynamicRegistration": true,
199 |                 "contentFormat": [
200 |                     "markdown",
201 |                     "plaintext"
202 |                 ]
203 |             },
204 |             "signatureHelp": {
205 |                 "dynamicRegistration": true,
206 |                 "signatureInformation": {
207 |                     "documentationFormat": [
208 |                         "markdown",
209 |                         "plaintext"
210 |                     ],
211 |                     "parameterInformation": {
212 |                         "labelOffsetSupport": true
213 |                     },
214 |                     "activeParameterSupport": true
215 |                 },
216 |                 "contextSupport": true
217 |             },
218 |             "definition": {
219 |                 "dynamicRegistration": true,
220 |                 "linkSupport": true
221 |             },
222 |             "references": {
223 |                 "dynamicRegistration": true
224 |             },
225 |             "documentHighlight": {
226 |                 "dynamicRegistration": true
227 |             },
228 |             "documentSymbol": {
229 |                 "dynamicRegistration": true,
230 |                 "symbolKind": {
231 |                     "valueSet": [
232 |                         1,
233 |                         2,
234 |                         3,
235 |                         4,
236 |                         5,
237 |                         6,
238 |                         7,
239 |                         8,
240 |                         9,
241 |                         10,
242 |                         11,
243 |                         12,
244 |                         13,
245 |                         14,
246 |                         15,
247 |                         16,
248 |                         17,
249 |                         18,
250 |                         19,
251 |                         20,
252 |                         21,
253 |                         22,
254 |                         23,
255 |                         24,
256 |                         25,
257 |                         26
258 |                     ]
259 |                 },
260 |                 "hierarchicalDocumentSymbolSupport": true,
261 |                 "tagSupport": {
262 |                     "valueSet": [
263 |                         1
264 |                     ]
265 |                 },
266 |                 "labelSupport": true
267 |             },
268 |             "codeAction": {
269 |                 "dynamicRegistration": true,
270 |                 "isPreferredSupport": true,
271 |                 "disabledSupport": true,
272 |                 "dataSupport": true,
273 |                 "resolveSupport": {
274 |                     "properties": [
275 |                         "edit"
276 |                     ]
277 |                 },
278 |                 "codeActionLiteralSupport": {
279 |                     "codeActionKind": {
280 |                         "valueSet": [
281 |                             "",
282 |                             "quickfix",
283 |                             "refactor",
284 |                             "refactor.extract",
285 |                             "refactor.inline",
286 |                             "refactor.rewrite",
287 |                             "source",
288 |                             "source.organizeImports"
289 |                         ]
290 |                     }
291 |                 },
292 |                 "honorsChangeAnnotations": false
293 |             },
294 |             "codeLens": {
295 |                 "dynamicRegistration": true
296 |             },
297 |             "formatting": {
298 |                 "dynamicRegistration": true
299 |             },
300 |             "rangeFormatting": {
301 |                 "dynamicRegistration": true
302 |             },
303 |             "onTypeFormatting": {
304 |                 "dynamicRegistration": true
305 |             },
306 |             "rename": {
307 |                 "dynamicRegistration": true,
308 |                 "prepareSupport": true,
309 |                 "prepareSupportDefaultBehavior": 1,
310 |                 "honorsChangeAnnotations": true
311 |             },
312 |             "documentLink": {
313 |                 "dynamicRegistration": true,
314 |                 "tooltipSupport": true
315 |             },
316 |             "typeDefinition": {
317 |                 "dynamicRegistration": true,
318 |                 "linkSupport": true
319 |             },
320 |             "implementation": {
321 |                 "dynamicRegistration": true,
322 |                 "linkSupport": true
323 |             },
324 |             "colorProvider": {
325 |                 "dynamicRegistration": true
326 |             },
327 |             "foldingRange": {
328 |                 "dynamicRegistration": true,
329 |                 "rangeLimit": 5000,
330 |                 "lineFoldingOnly": true,
331 |                 "foldingRangeKind": {
332 |                     "valueSet": [
333 |                         "comment",
334 |                         "imports",
335 |                         "region"
336 |                     ]
337 |                 },
338 |                 "foldingRange": {
339 |                     "collapsedText": false
340 |                 }
341 |             },
342 |             "declaration": {
343 |                 "dynamicRegistration": true,
344 |                 "linkSupport": true
345 |             },
346 |             "selectionRange": {
347 |                 "dynamicRegistration": true
348 |             },
349 |             "callHierarchy": {
350 |                 "dynamicRegistration": true
351 |             },
352 |             "semanticTokens": {
353 |                 "dynamicRegistration": true,
354 |                 "tokenTypes": [
355 |                     "namespace",
356 |                     "type",
357 |                     "class",
358 |                     "enum",
359 |                     "interface",
360 |                     "struct",
361 |                     "typeParameter",
362 |                     "parameter",
363 |                     "variable",
364 |                     "property",
365 |                     "enumMember",
366 |                     "event",
367 |                     "function",
368 |                     "method",
369 |                     "macro",
370 |                     "keyword",
371 |                     "modifier",
372 |                     "comment",
373 |                     "string",
374 |                     "number",
375 |                     "regexp",
376 |                     "operator",
377 |                     "decorator"
378 |                 ],
379 |                 "tokenModifiers": [
380 |                     "declaration",
381 |                     "definition",
382 |                     "readonly",
383 |                     "static",
384 |                     "deprecated",
385 |                     "abstract",
386 |                     "async",
387 |                     "modification",
388 |                     "documentation",
389 |                     "defaultLibrary"
390 |                 ],
391 |                 "formats": [
392 |                     "relative"
393 |                 ],
394 |                 "requests": {
395 |                     "range": true,
396 |                     "full": {
397 |                         "delta": true
398 |                     }
399 |                 },
400 |                 "multilineTokenSupport": false,
401 |                 "overlappingTokenSupport": false,
402 |                 "serverCancelSupport": true,
403 |                 "augmentsSyntaxTokens": false
404 |             },
405 |             "linkedEditingRange": {
406 |                 "dynamicRegistration": true
407 |             },
408 |             "typeHierarchy": {
409 |                 "dynamicRegistration": true
410 |             },
411 |             "inlineValue": {
412 |                 "dynamicRegistration": true
413 |             },
414 |             "inlayHint": {
415 |                 "dynamicRegistration": true,
416 |                 "resolveSupport": {
417 |                     "properties": [
418 |                         "tooltip",
419 |                         "textEdits",
420 |                         "label.tooltip",
421 |                         "label.location",
422 |                         "label.command"
423 |                     ]
424 |                 }
425 |             },
426 |             "diagnostic": {
427 |                 "dynamicRegistration": true,
428 |                 "relatedDocumentSupport": false
429 |             }
430 |         },
431 |         "window": {
432 |             "showMessage": {
433 |                 "messageActionItem": {
434 |                     "additionalPropertiesSupport": true
435 |                 }
436 |             },
437 |             "showDocument": {
438 |                 "support": true
439 |             },
440 |             "workDoneProgress": true
441 |         },
442 |         "general": {
443 |             "staleRequestSupport": {
444 |                 "cancel": true,
445 |                 "retryOnContentModified": [
446 |                     "textDocument/semanticTokens/full",
447 |                     "textDocument/semanticTokens/range",
448 |                     "textDocument/semanticTokens/full/delta"
449 |                 ]
450 |             },
451 |             "regularExpressions": {
452 |                 "engine": "ECMAScript",
453 |                 "version": "ES2020"
454 |             },
455 |             "markdown": {
456 |                 "parser": "marked",
457 |                 "version": "1.1.0",
458 |                 "allowedTags": [
459 |                     "ul",
460 |                     "li",
461 |                     "p",
462 |                     "code",
463 |                     "blockquote",
464 |                     "ol",
465 |                     "h1",
466 |                     "h2",
467 |                     "h3",
468 |                     "h4",
469 |                     "h5",
470 |                     "h6",
471 |                     "hr",
472 |                     "em",
473 |                     "pre",
474 |                     "table",
475 |                     "thead",
476 |                     "tbody",
477 |                     "tr",
478 |                     "th",
479 |                     "td",
480 |                     "div",
481 |                     "del",
482 |                     "a",
483 |                     "strong",
484 |                     "br",
485 |                     "img",
486 |                     "span"
487 |                 ]
488 |             },
489 |             "positionEncodings": [
490 |                 "utf-16"
491 |             ]
492 |         },
493 |         "notebookDocument": {
494 |             "synchronization": {
495 |                 "dynamicRegistration": true,
496 |                 "executionSummarySupport": true
497 |             }
498 |         },
499 |         "experimental": {
500 |             "snippetTextEdit": true,
501 |             "codeActionGroup": true,
502 |             "hoverActions": true,
503 |             "serverStatusNotification": true,
504 |             "colorDiagnosticOutput": true,
505 |             "openServerLogs": true,
506 |             "commands": {
507 |                 "commands": [
508 |                     "editor.action.triggerParameterHints"
509 |                 ]
510 |             }
511 |         }
512 |     },
513 |     "initializationOptions": {
514 |         "RoslynExtensionsOptions": {
515 |             "EnableDecompilationSupport": false,
516 |             "EnableAnalyzersSupport": true,
517 |             "EnableImportCompletion": true,
518 |             "EnableAsyncCompletion": false,
519 |             "DocumentAnalysisTimeoutMs": 30000,
520 |             "DiagnosticWorkersThreadCount": 18,
521 |             "AnalyzeOpenDocumentsOnly": true,
522 |             "InlayHintsOptions": {
523 |                 "EnableForParameters": false,
524 |                 "ForLiteralParameters": false,
525 |                 "ForIndexerParameters": false,
526 |                 "ForObjectCreationParameters": false,
527 |                 "ForOtherParameters": false,
528 |                 "SuppressForParametersThatDifferOnlyBySuffix": false,
529 |                 "SuppressForParametersThatMatchMethodIntent": false,
530 |                 "SuppressForParametersThatMatchArgumentName": false,
531 |                 "EnableForTypes": false,
532 |                 "ForImplicitVariableTypes": false,
533 |                 "ForLambdaParameterTypes": false,
534 |                 "ForImplicitObjectCreation": false
535 |             },
536 |             "LocationPaths": null
537 |         },
538 |         "FormattingOptions": {
539 |             "OrganizeImports": false,
540 |             "EnableEditorConfigSupport": true,
541 |             "NewLine": "\n",
542 |             "UseTabs": false,
543 |             "TabSize": 4,
544 |             "IndentationSize": 4,
545 |             "SpacingAfterMethodDeclarationName": false,
546 |             "SeparateImportDirectiveGroups": false,
547 |             "SpaceWithinMethodDeclarationParenthesis": false,
548 |             "SpaceBetweenEmptyMethodDeclarationParentheses": false,
549 |             "SpaceAfterMethodCallName": false,
550 |             "SpaceWithinMethodCallParentheses": false,
551 |             "SpaceBetweenEmptyMethodCallParentheses": false,
552 |             "SpaceAfterControlFlowStatementKeyword": true,
553 |             "SpaceWithinExpressionParentheses": false,
554 |             "SpaceWithinCastParentheses": false,
555 |             "SpaceWithinOtherParentheses": false,
556 |             "SpaceAfterCast": false,
557 |             "SpaceBeforeOpenSquareBracket": false,
558 |             "SpaceBetweenEmptySquareBrackets": false,
559 |             "SpaceWithinSquareBrackets": false,
560 |             "SpaceAfterColonInBaseTypeDeclaration": true,
561 |             "SpaceAfterComma": true,
562 |             "SpaceAfterDot": false,
563 |             "SpaceAfterSemicolonsInForStatement": true,
564 |             "SpaceBeforeColonInBaseTypeDeclaration": true,
565 |             "SpaceBeforeComma": false,
566 |             "SpaceBeforeDot": false,
567 |             "SpaceBeforeSemicolonsInForStatement": false,
568 |             "SpacingAroundBinaryOperator": "single",
569 |             "IndentBraces": false,
570 |             "IndentBlock": true,
571 |             "IndentSwitchSection": true,
572 |             "IndentSwitchCaseSection": true,
573 |             "IndentSwitchCaseSectionWhenBlock": true,
574 |             "LabelPositioning": "oneLess",
575 |             "WrappingPreserveSingleLine": true,
576 |             "WrappingKeepStatementsOnSingleLine": true,
577 |             "NewLinesForBracesInTypes": true,
578 |             "NewLinesForBracesInMethods": true,
579 |             "NewLinesForBracesInProperties": true,
580 |             "NewLinesForBracesInAccessors": true,
581 |             "NewLinesForBracesInAnonymousMethods": true,
582 |             "NewLinesForBracesInControlBlocks": true,
583 |             "NewLinesForBracesInAnonymousTypes": true,
584 |             "NewLinesForBracesInObjectCollectionArrayInitializers": true,
585 |             "NewLinesForBracesInLambdaExpressionBody": true,
586 |             "NewLineForElse": true,
587 |             "NewLineForCatch": true,
588 |             "NewLineForFinally": true,
589 |             "NewLineForMembersInObjectInit": true,
590 |             "NewLineForMembersInAnonymousTypes": true,
591 |             "NewLineForClausesInQuery": true
592 |         },
593 |         "FileOptions": {
594 |             "SystemExcludeSearchPatterns": [
595 |                 "**/node_modules/**/*",
596 |                 "**/bin/**/*",
597 |                 "**/obj/**/*",
598 |                 "**/.git/**/*",
599 |                 "**/.git",
600 |                 "**/.svn",
601 |                 "**/.hg",
602 |                 "**/CVS",
603 |                 "**/.DS_Store",
604 |                 "**/Thumbs.db"
605 |             ],
606 |             "ExcludeSearchPatterns": []
607 |         },
608 |         "RenameOptions": {
609 |             "RenameOverloads": false,
610 |             "RenameInStrings": false,
611 |             "RenameInComments": false
612 |         },
613 |         "ImplementTypeOptions": {
614 |             "InsertionBehavior": 0,
615 |             "PropertyGenerationBehavior": 0
616 |         },
617 |         "DotNetCliOptions": {
618 |             "LocationPaths": null
619 |         },
620 |         "Plugins": {
621 |             "LocationPaths": null
622 |         }
623 |     },
624 |     "trace": "verbose",
625 |     "workspaceFolders": [
626 |         {
627 |             "uri": "$uri",
628 |             "name": "$name"
629 |         }
630 |     ]
631 | }
```
Page 12/21FirstPrevNextLast