mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-12 16:15:33 +00:00
Added partial Groovy language support (#833)
This commit is contained in:
1 parent
b193e50a72
commit
47bc4e0b32
14 files changed
+516
-1
No files matched your search
@@ -35,6 +35,7 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
* Language support:
|
||||
|
||||
* **Add support for Fortran** via fortls language server (requires `pip install fortls`)
|
||||
* **Add partial support for Groovy** requires user-provided Groovy language server JAR (see [setup guide](docs/03-special-guides/groovy_setup_guide_for_serena.md))
|
||||
* **Add support for Julia** via LanguageServer.jl
|
||||
* **Add support for Haskell** via Haskell Language Server (HLS) with automatic discovery via ghcup, stack, or system PATH; supports both Stack and Cabal projects
|
||||
* **Add support for Scala** via Metals language server (requires some [manual setup](docs/03-special-guides/scala_setup_guide_for_serena.md))
|
||||
|
||||
@@ -68,7 +68,7 @@ than existing solutions that charge a premium.
|
||||
|
||||
Language servers provide support for a wide range of programming languages.
|
||||
With Serena's LSP library, we provide **support for over 30 programming languages**, including
|
||||
AL, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, Go, Haskell, Java, Javascript, Julia, Kotlin, Lua, Markdown, Nix, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Swift, TOML, TypeScript, YAML, and Zig.
|
||||
AL, Bash, C#, C/C++, Clojure, Dart, Elixir, Elm, Erlang, Fortran, Go, Groovy (partial support), Haskell, Java, Javascript, Julia, Kotlin, Lua, Markdown, Nix, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Swift, TOML, TypeScript, YAML, and Zig.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Some languages require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details.
|
||||
|
||||
@@ -31,6 +31,8 @@ With Serena, we provide direct, out-of-the-box support for:
|
||||
(requires installation of fortls: `pip install fortls`)
|
||||
* **Go**
|
||||
(requires installation of `gopls`)
|
||||
* **Groovy**
|
||||
(requires local groovy-language-server.jar setup via GROOVY_LS_JAR_PATH or configuration)
|
||||
* **Haskell**
|
||||
(automatically locates HLS via ghcup, stack, or system PATH; supports Stack and Cabal projects)
|
||||
* **Java**
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# Groovy Setup Guide for Serena
|
||||
|
||||
The Groovy support in Serena is incomplete and requires the user to provide a functioning,
|
||||
JVM-based Groovy language server as a jar. This intermediate state allows the contributors
|
||||
of Groovy support to use Serena internally and hopefully to accelerate their open-source
|
||||
release of a Groovy language server in the future.
|
||||
|
||||
If you happen to have a Groovy language server JAR file, you can configure Serena to use it
|
||||
by following the instructions below.
|
||||
|
||||
---
|
||||
## Prerequisites
|
||||
|
||||
- Groovy Language Server JAR file
|
||||
- Can be any open-source Groovy language server or your custom implementation
|
||||
- The JAR must be compatible with standard LSP protocol
|
||||
|
||||
---
|
||||
## Configuration
|
||||
|
||||
Configure Groovy Language Server by adding settings to your `~/.serena/serena_config.yml`:
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
groovy:
|
||||
ls_jar_path: '/path/to/groovy-language-server.jar'
|
||||
ls_jar_options: '-Xmx2G -Xms512m'
|
||||
```
|
||||
|
||||
### Custom Java Paths
|
||||
|
||||
If you have specific Java installations:
|
||||
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
groovy:
|
||||
ls_jar_path: '/path/to/groovy-language-server.jar'
|
||||
ls_java_home_path: '/usr/lib/jvm/java-21-openjdk' # Custom JAVA_HOME directory
|
||||
ls_jar_options: '-Xmx2G -Xms512m' # Optional JVM options
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
- `ls_jar_path`: Absolute path to your Groovy Language Server JAR file (required)
|
||||
- `ls_java_home_path`: Custom JAVA_HOME directory for Java installation (optional)
|
||||
- When specified, Serena will use this Java installation instead of downloading bundled Java
|
||||
- Java executable path is automatically determined based on platform:
|
||||
- Windows: `{ls_java_home_path}/bin/java.exe`
|
||||
- Linux/macOS: `{ls_java_home_path}/bin/java`
|
||||
- Validates that Java executable exists at the expected location
|
||||
- `ls_jar_options`: JVM options for language server (optional)
|
||||
- Common options:
|
||||
- `-Xmx<size>`: Maximum heap size (e.g., `-Xmx2G` for 2GB)
|
||||
- `-Xms<size>`: Initial heap size (e.g., `-Xms512m` for 512MB)
|
||||
|
||||
---
|
||||
## Project Structure Requirements
|
||||
|
||||
For optimal Groovy Language Server performance, ensure your project follows standard Groovy/Gradle structure:
|
||||
|
||||
```
|
||||
project-root/
|
||||
├── src/
|
||||
│ ├── main/
|
||||
│ │ ├── groovy/
|
||||
│ │ └── resources/
|
||||
│ └── test/
|
||||
│ ├── groovy/
|
||||
│ └── resources/
|
||||
├── build.gradle or build.gradle.kts
|
||||
├── settings.gradle or settings.gradle.kts
|
||||
└── gradle/
|
||||
└── wrapper/
|
||||
```
|
||||
|
||||
---
|
||||
## Using Serena with Groovy
|
||||
|
||||
- Serena automatically detects Groovy files (`*.groovy`, `*.gvy`) and will start a Groovy Language Server JAR process per project when needed.
|
||||
- Optimal results require that your project compiles successfully via Gradle or Maven. If compilation fails, fix build errors in your build tool first.
|
||||
|
||||
## Reference
|
||||
|
||||
- **Groovy Documentation**: [https://groovy-lang.org/documentation.html](https://groovy-lang.org/documentation.html)
|
||||
- **Gradle Documentation**: [https://docs.gradle.org](https://docs.gradle.org)
|
||||
- **Serena Configuration**: [../02-usage/050_configuration.md](../02-usage/050_configuration.md)
|
||||
@@ -273,6 +273,7 @@ markers = [
|
||||
"go: language server running for Go",
|
||||
"java: language server running for Java",
|
||||
"kotlin: language server running for kotlin",
|
||||
"groovy: language server running for Groovy",
|
||||
"rust: language server running for Rust",
|
||||
"typescript: language server running for TypeScript",
|
||||
"vue: language server running for Vue (uses TypeScript LSP)",
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
"""
|
||||
Provides Groovy specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Groovy.
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import shlex
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class GroovyRuntimeDependencyPaths:
|
||||
"""
|
||||
Stores the paths to the runtime dependencies of Groovy Language Server
|
||||
"""
|
||||
|
||||
java_path: str
|
||||
java_home_path: str
|
||||
ls_jar_path: str
|
||||
groovy_home_path: str | None = None
|
||||
|
||||
|
||||
class GroovyLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Groovy specific instantiation of the LanguageServer class.
|
||||
Contains various configurations and settings specific to Groovy.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
"""
|
||||
Creates a Groovy Language Server instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
runtime_dependency_paths = self._setup_runtime_dependencies(solidlsp_settings)
|
||||
self.runtime_dependency_paths = runtime_dependency_paths
|
||||
|
||||
# Get jar options from configuration
|
||||
ls_jar_options = []
|
||||
|
||||
if solidlsp_settings.ls_specific_settings:
|
||||
groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY)
|
||||
jar_options_str = groovy_settings.get("ls_jar_options", "")
|
||||
if jar_options_str:
|
||||
ls_jar_options = shlex.split(jar_options_str)
|
||||
log.info(f"Using Groovy LS JAR options from configuration: {jar_options_str}")
|
||||
|
||||
# Create command to execute the Groovy Language Server
|
||||
cmd = [self.runtime_dependency_paths.java_path, "-jar", self.runtime_dependency_paths.ls_jar_path]
|
||||
cmd.extend(ls_jar_options)
|
||||
|
||||
# Set environment variables including JAVA_HOME
|
||||
proc_env = {"JAVA_HOME": self.runtime_dependency_paths.java_home_path}
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=cmd, env=proc_env, cwd=repository_root_path),
|
||||
"groovy",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
log.info(f"Starting Groovy Language Server with jar options: {ls_jar_options}")
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> GroovyRuntimeDependencyPaths:
|
||||
"""
|
||||
Setup runtime dependencies for Groovy Language Server and return paths.
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
# Verify platform support
|
||||
assert (
|
||||
platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-")
|
||||
), "Only Windows, Linux and macOS platforms are supported for Groovy in multilspy at the moment"
|
||||
|
||||
# Check if user specified custom Java home path
|
||||
java_home_path = None
|
||||
java_path = None
|
||||
|
||||
if solidlsp_settings and solidlsp_settings.ls_specific_settings:
|
||||
groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY)
|
||||
custom_java_home = groovy_settings.get("ls_java_home_path")
|
||||
if custom_java_home:
|
||||
log.info(f"Using custom Java home path from configuration: {custom_java_home}")
|
||||
java_home_path = custom_java_home
|
||||
|
||||
# Determine java executable path based on platform
|
||||
if platform_id.value.startswith("win-"):
|
||||
java_path = os.path.join(java_home_path, "bin", "java.exe")
|
||||
else:
|
||||
java_path = os.path.join(java_home_path, "bin", "java")
|
||||
|
||||
# If no custom Java home path, download and use bundled Java
|
||||
if java_home_path is None:
|
||||
# Runtime dependency information
|
||||
runtime_dependencies = {
|
||||
"java": {
|
||||
"win-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-win32-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe",
|
||||
},
|
||||
"linux-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-linux-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-linux-x86_64/bin/java",
|
||||
},
|
||||
"linux-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-linux-aarch64",
|
||||
"java_path": "extension/jre/21.0.7-linux-aarch64/bin/java",
|
||||
},
|
||||
"osx-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-macosx-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-macosx-x86_64/bin/java",
|
||||
},
|
||||
"osx-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-macosx-aarch64",
|
||||
"java_path": "extension/jre/21.0.7-macosx-aarch64/bin/java",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
java_dependency = runtime_dependencies["java"][platform_id.value]
|
||||
|
||||
static_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "groovy_language_server")
|
||||
os.makedirs(static_dir, exist_ok=True)
|
||||
|
||||
java_dir = os.path.join(static_dir, "java")
|
||||
os.makedirs(java_dir, exist_ok=True)
|
||||
|
||||
java_home_path = os.path.join(java_dir, java_dependency["java_home_path"])
|
||||
java_path = os.path.join(java_dir, java_dependency["java_path"])
|
||||
|
||||
if not os.path.exists(java_path):
|
||||
log.info(f"Downloading Java for {platform_id.value}...")
|
||||
FileUtils.download_and_extract_archive(java_dependency["url"], java_dir, java_dependency["archiveType"])
|
||||
|
||||
if not platform_id.value.startswith("win-"):
|
||||
os.chmod(java_path, 0o755)
|
||||
|
||||
assert java_path and os.path.exists(java_path), f"Java executable not found at {java_path}"
|
||||
|
||||
ls_jar_path = cls._find_groovy_ls_jar(solidlsp_settings)
|
||||
|
||||
return GroovyRuntimeDependencyPaths(java_path=java_path, java_home_path=java_home_path, ls_jar_path=ls_jar_path)
|
||||
|
||||
@classmethod
|
||||
def _find_groovy_ls_jar(cls, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""
|
||||
Find Groovy Language Server JAR file
|
||||
"""
|
||||
if solidlsp_settings and solidlsp_settings.ls_specific_settings:
|
||||
groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY)
|
||||
config_jar_path = groovy_settings.get("ls_jar_path")
|
||||
if config_jar_path and os.path.exists(config_jar_path):
|
||||
log.info(f"Using Groovy LS JAR from configuration: {config_jar_path}")
|
||||
return config_jar_path
|
||||
|
||||
# if JAR not found
|
||||
raise RuntimeError(
|
||||
"Groovy Language Server JAR not found. To use Groovy language support:\n"
|
||||
"Set 'ls_jar_path' in groovy settings in serena_config.yml:\n"
|
||||
" ls_specific_settings:\n"
|
||||
" groovy:\n"
|
||||
" ls_jar_path: '/path/to/groovy-language-server.jar'\n"
|
||||
" Ensure the JAR file is available at the configured path\n"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the Groovy Language Server.
|
||||
"""
|
||||
if not os.path.isabs(repository_absolute_path):
|
||||
repository_absolute_path = os.path.abspath(repository_absolute_path)
|
||||
|
||||
root_uri = pathlib.Path(repository_absolute_path).as_uri()
|
||||
initialize_params = {
|
||||
"clientInfo": {"name": "Serena Groovy Client", "version": "1.0.0"},
|
||||
"rootPath": repository_absolute_path,
|
||||
"rootUri": root_uri,
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {"dynamicRegistration": True, "didSave": True},
|
||||
"completion": {"dynamicRegistration": True},
|
||||
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
|
||||
"definition": {"dynamicRegistration": True},
|
||||
"references": {"dynamicRegistration": True},
|
||||
"documentSymbol": {"dynamicRegistration": True},
|
||||
"workspaceSymbol": {"dynamicRegistration": True},
|
||||
"signatureHelp": {"dynamicRegistration": True},
|
||||
"rename": {"dynamicRegistration": True},
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": True,
|
||||
},
|
||||
},
|
||||
"initializationOptions": {
|
||||
"settings": {
|
||||
"groovy": {
|
||||
"classpath": [],
|
||||
"diagnostics": {"enabled": True},
|
||||
"completion": {"enabled": True},
|
||||
}
|
||||
},
|
||||
},
|
||||
"processId": os.getpid(),
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": root_uri,
|
||||
"name": os.path.basename(repository_absolute_path),
|
||||
}
|
||||
],
|
||||
}
|
||||
return initialize_params # type: ignore
|
||||
|
||||
def _start_server(self) -> None:
|
||||
"""
|
||||
Starts the Groovy Language Server
|
||||
"""
|
||||
|
||||
def execute_client_command_handler(params: dict) -> list:
|
||||
return []
|
||||
|
||||
def do_nothing(params: dict) -> None:
|
||||
return
|
||||
|
||||
def window_log_message(msg: dict) -> None:
|
||||
log.info(f"LSP: window/logMessage: {msg}")
|
||||
|
||||
self.server.on_request("client/registerCapability", do_nothing)
|
||||
self.server.on_notification("language/status", do_nothing)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
|
||||
log.info("Starting Groovy server process")
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
capabilities = init_response["capabilities"]
|
||||
assert "textDocumentSync" in capabilities, "Server must support textDocumentSync"
|
||||
assert "hoverProvider" in capabilities, "Server must support hover"
|
||||
assert "completionProvider" in capabilities, "Server must support code completion"
|
||||
assert "signatureHelpProvider" in capabilities, "Server must support signature help"
|
||||
assert "definitionProvider" in capabilities, "Server must support go to definition"
|
||||
assert "referencesProvider" in capabilities, "Server must support find references"
|
||||
assert "documentSymbolProvider" in capabilities, "Server must support document symbols"
|
||||
assert "workspaceSymbolProvider" in capabilities, "Server must support workspace symbols"
|
||||
|
||||
self.server.notify.initialized({})
|
||||
self.completions_available.set()
|
||||
@@ -61,6 +61,7 @@ class Language(str, Enum):
|
||||
JULIA = "julia"
|
||||
FORTRAN = "fortran"
|
||||
HASKELL = "haskell"
|
||||
GROOVY = "groovy"
|
||||
VUE = "vue"
|
||||
POWERSHELL = "powershell"
|
||||
# Experimental or deprecated Language Servers
|
||||
@@ -112,6 +113,7 @@ class Language(str, Enum):
|
||||
self.MARKDOWN,
|
||||
self.YAML,
|
||||
self.TOML,
|
||||
self.GROOVY,
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -221,6 +223,8 @@ class Language(str, Enum):
|
||||
return FilenameMatcher(*path_patterns)
|
||||
case self.POWERSHELL:
|
||||
return FilenameMatcher("*.ps1", "*.psm1", "*.psd1")
|
||||
case self.GROOVY:
|
||||
return FilenameMatcher("*.groovy", "*.gvy")
|
||||
case _:
|
||||
raise ValueError(f"Unhandled language: {self}")
|
||||
|
||||
@@ -382,6 +386,10 @@ class Language(str, Enum):
|
||||
from solidlsp.language_servers.powershell_language_server import PowerShellLanguageServer
|
||||
|
||||
return PowerShellLanguageServer
|
||||
case self.GROOVY:
|
||||
from solidlsp.language_servers.groovy_language_server import GroovyLanguageServer
|
||||
|
||||
return GroovyLanguageServer
|
||||
case _:
|
||||
raise ValueError(f"Unhandled language: {self}")
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.gradle/
|
||||
@@ -0,0 +1,7 @@
|
||||
plugins {
|
||||
id 'groovy'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.example
|
||||
|
||||
class Main {
|
||||
static void main(String[] args) {
|
||||
Utils.printHello()
|
||||
Model model = new Model("Cascade")
|
||||
println(model.name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.example
|
||||
|
||||
class Model {
|
||||
String name
|
||||
|
||||
Model(String name) {
|
||||
this.name = name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.example
|
||||
|
||||
class ModelUser {
|
||||
static void main(String[] args) {
|
||||
Model model = new Model("Cascade")
|
||||
println(model.name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.example
|
||||
|
||||
class Utils {
|
||||
static void printHello() {
|
||||
println("Hello from Utils!")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.constants import SERENA_MANAGED_DIR_NAME
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
@pytest.mark.groovy
|
||||
class TestGroovyLanguageServer:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
"""
|
||||
Set up test class with Groovy test repository.
|
||||
"""
|
||||
cls.test_repo_path = Path(__file__).parent.parent.parent / "resources" / "repos" / "groovy" / "test_repo"
|
||||
|
||||
if not cls.test_repo_path.exists():
|
||||
pytest.skip("Groovy test repository not found")
|
||||
|
||||
# Use JAR path from environment variable
|
||||
ls_jar_path = os.environ.get("GROOVY_LS_JAR_PATH")
|
||||
if not ls_jar_path or not os.path.exists(ls_jar_path):
|
||||
pytest.skip(
|
||||
"Groovy Language Server JAR not found. Set GROOVY_LS_JAR_PATH environment variable to run tests.",
|
||||
allow_module_level=True,
|
||||
)
|
||||
|
||||
# Get JAR options from environment variable
|
||||
ls_jar_options = os.environ.get("GROOVY_LS_JAR_OPTIONS", "")
|
||||
ls_java_home_path = os.environ.get("GROOVY_LS_JAVA_HOME_PATH")
|
||||
|
||||
groovy_settings = {"ls_jar_path": ls_jar_path, "ls_jar_options": ls_jar_options}
|
||||
if ls_java_home_path:
|
||||
groovy_settings["ls_java_home_path"] = ls_java_home_path
|
||||
|
||||
# Create language server directly with Groovy-specific settings
|
||||
repo_path = str(cls.test_repo_path)
|
||||
config = LanguageServerConfig(code_language=Language.GROOVY, ignored_paths=[], trace_lsp_communication=False)
|
||||
|
||||
solidlsp_settings = SolidLSPSettings(
|
||||
solidlsp_dir=str(Path.home() / ".serena"),
|
||||
project_data_relative_path=SERENA_MANAGED_DIR_NAME,
|
||||
ls_specific_settings={Language.GROOVY: groovy_settings},
|
||||
)
|
||||
|
||||
cls.language_server = SolidLanguageServer.create(config, repo_path, solidlsp_settings=solidlsp_settings)
|
||||
cls.language_server.start()
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
"""
|
||||
Clean up language server.
|
||||
"""
|
||||
if hasattr(cls, "language_server"):
|
||||
cls.language_server.stop()
|
||||
|
||||
def test_find_symbol(self) -> None:
|
||||
symbols = self.language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser class not found in symbol tree"
|
||||
|
||||
def test_find_referencing_class_symbols(self) -> None:
|
||||
file_path = os.path.join("src", "main", "groovy", "com", "example", "Utils.groovy")
|
||||
refs = self.language_server.request_references(file_path, 3, 6)
|
||||
assert any("Main.groovy" in ref.get("relativePath", "") for ref in refs), "Utils should be referenced from Main.groovy"
|
||||
|
||||
file_path = os.path.join("src", "main", "groovy", "com", "example", "Model.groovy")
|
||||
symbols = self.language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
model_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "com.example.Model" and sym.get("kind") == 5:
|
||||
model_symbol = sym
|
||||
break
|
||||
assert model_symbol is not None, "Could not find 'Model' class symbol in Model.groovy"
|
||||
|
||||
if "selectionRange" in model_symbol:
|
||||
sel_start = model_symbol["selectionRange"]["start"]
|
||||
else:
|
||||
sel_start = model_symbol["range"]["start"]
|
||||
refs = self.language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
|
||||
main_refs = [ref for ref in refs if "Main.groovy" in ref.get("relativePath", "")]
|
||||
assert len(main_refs) >= 2, f"Model should be referenced from Main.groovy at least 2 times, found {len(main_refs)}"
|
||||
|
||||
model_user_refs = [ref for ref in refs if "ModelUser.groovy" in ref.get("relativePath", "")]
|
||||
assert len(model_user_refs) >= 1, f"Model should be referenced from ModelUser.groovy at least 1 time, found {len(model_user_refs)}"
|
||||
|
||||
def test_overview_methods(self) -> None:
|
||||
symbols = self.language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser missing from overview"
|
||||
Reference in new issue
Block a user