mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 00:16:57 +00:00
Merge pull request #1030 from oraios/fix-symbol-body-end
Handle end_col when extracting symbol body
This commit is contained in:
12 files changed
+282
-253
No files matched your search
@@ -209,7 +209,7 @@ ls_specific_settings:
|
||||
```
|
||||
|
||||
This is supported by all language servers deriving their dependency provider from `LanguageServerDependencyProviderSinglePath`.
|
||||
Currently, this includes the following languages: `clojure`, `cpp`, `php`, `python`, `rust`, `typescript`.
|
||||
Currently, this includes the following languages: `bash`, `clojure`, `cpp`, `markdown`, `php`, `php_phpactor`, `python`, `rust`, `toml`, `typescript`, `yaml`.
|
||||
We will add support for more languages over time.
|
||||
|
||||
#### C# (Roslyn Language Server)
|
||||
|
||||
@@ -10,10 +10,15 @@ import shutil
|
||||
import threading
|
||||
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
|
||||
from solidlsp.ls import (
|
||||
DocumentSymbols,
|
||||
LanguageServerDependencyProvider,
|
||||
LanguageServerDependencyProviderSinglePath,
|
||||
LSPFileBuffer,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
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__)
|
||||
@@ -30,57 +35,63 @@ class BashLanguageServer(SolidLanguageServer):
|
||||
Creates a BashLanguageServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
bash_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=bash_lsp_executable_path, cwd=repository_root_path),
|
||||
None,
|
||||
"bash",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Bash Language Server and return the command to start the server.
|
||||
"""
|
||||
# Verify both node and npm are installed
|
||||
is_node_installed = shutil.which("node") is not None
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="bash-language-server",
|
||||
description="bash-language-server package",
|
||||
command="npm install --prefix ./ bash-language-server@5.6.0",
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
)
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Bash Language Server and return the command to start the server.
|
||||
"""
|
||||
# Verify both node and npm are installed
|
||||
is_node_installed = shutil.which("node") is not None
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
|
||||
# Install bash-language-server if not already installed
|
||||
bash_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "bash-lsp")
|
||||
bash_executable_path = os.path.join(bash_ls_dir, "node_modules", ".bin", "bash-language-server")
|
||||
|
||||
# Handle Windows executable extension
|
||||
if os.name == "nt":
|
||||
bash_executable_path += ".cmd"
|
||||
|
||||
if not os.path.exists(bash_executable_path):
|
||||
log.info(f"Bash Language Server executable not found at {bash_executable_path}. Installing...")
|
||||
deps.install(bash_ls_dir)
|
||||
log.info("Bash language server dependencies installed successfully")
|
||||
|
||||
if not os.path.exists(bash_executable_path):
|
||||
raise FileNotFoundError(
|
||||
f"bash-language-server executable not found at {bash_executable_path}, something went wrong with the installation."
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="bash-language-server",
|
||||
description="bash-language-server package",
|
||||
command="npm install --prefix ./ bash-language-server@5.6.0",
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
)
|
||||
return f"{bash_executable_path} start"
|
||||
|
||||
# Install bash-language-server if not already installed
|
||||
bash_ls_dir = os.path.join(self._ls_resources_dir, "bash-lsp")
|
||||
bash_executable_path = os.path.join(bash_ls_dir, "node_modules", ".bin", "bash-language-server")
|
||||
|
||||
# Handle Windows executable extension
|
||||
if os.name == "nt":
|
||||
bash_executable_path += ".cmd"
|
||||
|
||||
if not os.path.exists(bash_executable_path):
|
||||
log.info(f"Bash Language Server executable not found at {bash_executable_path}. Installing...")
|
||||
deps.install(bash_ls_dir)
|
||||
log.info("Bash language server dependencies installed successfully")
|
||||
|
||||
if not os.path.exists(bash_executable_path):
|
||||
raise FileNotFoundError(
|
||||
f"bash-language-server executable not found at {bash_executable_path}, something went wrong with the installation."
|
||||
)
|
||||
|
||||
return bash_executable_path
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path, "start"]
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
|
||||
@@ -91,7 +91,7 @@ class CCLS(SolidLanguageServer):
|
||||
log.info(f"Using system-installed ccls at {ccls_path}")
|
||||
return ccls_path
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str] | str:
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path]
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -123,7 +123,7 @@ class ClojureLSP(SolidLanguageServer):
|
||||
os.chmod(clojurelsp_executable_path, 0o755)
|
||||
return clojurelsp_executable_path
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str] | str:
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path]
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -306,7 +306,7 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
self._repository_root_path = repository_root_path
|
||||
self._dotnet_path, self._language_server_path = self._ensure_server_installed()
|
||||
|
||||
def create_launch_command(self) -> list[str] | str:
|
||||
def create_launch_command(self) -> list[str]:
|
||||
# Find solution or project file
|
||||
solution_or_project = find_solution_or_project_file(self._repository_root_path)
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
intellisense_members_path=intellisense_members_path,
|
||||
)
|
||||
|
||||
def create_launch_command(self) -> list[str] | str:
|
||||
def create_launch_command(self) -> list[str]:
|
||||
# ws_dir is the workspace directory for the EclipseJDTLS server
|
||||
ws_dir = str(
|
||||
PurePath(
|
||||
|
||||
@@ -9,10 +9,9 @@ import pathlib
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
@@ -25,80 +24,84 @@ class Marksman(SolidLanguageServer):
|
||||
Provides Markdown specific instantiation of the LanguageServer class using marksman.
|
||||
"""
|
||||
|
||||
marksman_releases = "https://github.com/artempyanykh/marksman/releases/download/2024-12-18"
|
||||
runtime_dependencies = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-linux-x64",
|
||||
platform_id="linux-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-linux-arm64",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-macos",
|
||||
platform_id="osx-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-macos",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman.exe",
|
||||
platform_id="win-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman.exe",
|
||||
),
|
||||
]
|
||||
)
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
marksman_releases = "https://github.com/artempyanykh/marksman/releases/download/2024-12-18"
|
||||
runtime_dependencies = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-linux-x64",
|
||||
platform_id="linux-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-linux-arm64",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-macos",
|
||||
platform_id="osx-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman-macos",
|
||||
platform_id="osx-arm64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="marksman",
|
||||
url=f"{marksman_releases}/marksman.exe",
|
||||
platform_id="win-x64",
|
||||
archive_type="binary",
|
||||
binary_name="marksman.exe",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""Setup runtime dependencies for marksman and return the command to start the server."""
|
||||
deps = cls.runtime_dependencies
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""Setup runtime dependencies for marksman and return the command to start the server."""
|
||||
deps = self.runtime_dependencies
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
|
||||
marksman_ls_dir = cls.ls_resources_dir(solidlsp_settings)
|
||||
marksman_executable_path = deps.binary_path(marksman_ls_dir)
|
||||
if not os.path.exists(marksman_executable_path):
|
||||
log.info(
|
||||
f"Downloading marksman from {dependency.url} to {marksman_ls_dir}",
|
||||
)
|
||||
deps.install(marksman_ls_dir)
|
||||
if not os.path.exists(marksman_executable_path):
|
||||
raise FileNotFoundError(f"Download failed? Could not find marksman executable at {marksman_executable_path}")
|
||||
os.chmod(marksman_executable_path, 0o755)
|
||||
return marksman_executable_path
|
||||
marksman_ls_dir = self._ls_resources_dir
|
||||
marksman_executable_path = deps.binary_path(marksman_ls_dir)
|
||||
if not os.path.exists(marksman_executable_path):
|
||||
log.info(
|
||||
f"Downloading marksman from {dependency.url} to {marksman_ls_dir}",
|
||||
)
|
||||
deps.install(marksman_ls_dir)
|
||||
if not os.path.exists(marksman_executable_path):
|
||||
raise FileNotFoundError(f"Download failed? Could not find marksman executable at {marksman_executable_path}")
|
||||
os.chmod(marksman_executable_path, 0o755)
|
||||
return marksman_executable_path
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path, "server"]
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
"""
|
||||
Creates a Marksman instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
marksman_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=f"{marksman_executable_path} server", cwd=repository_root_path),
|
||||
None,
|
||||
"markdown",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["node_modules", ".obsidian", ".vitepress", ".vuepress"]
|
||||
|
||||
@@ -38,7 +38,7 @@ import platform
|
||||
import shutil
|
||||
import threading
|
||||
import zipfile
|
||||
from typing import Any, Union, cast
|
||||
from typing import Any, cast
|
||||
|
||||
import requests
|
||||
|
||||
@@ -339,7 +339,7 @@ class MatlabLanguageServer(SolidLanguageServer):
|
||||
self._matlab_path = matlab_path
|
||||
return matlab_path
|
||||
|
||||
def create_launch_command(self) -> Union[str, list[str]]:
|
||||
def create_launch_command(self) -> list[str]:
|
||||
# Verify node is installed
|
||||
node_path = shutil.which("node")
|
||||
if node_path is None:
|
||||
|
||||
@@ -165,7 +165,7 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
return self._ensure_rust_analyzer_installed()
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str] | str:
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path]
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
|
||||
@@ -17,11 +17,10 @@ from typing import Any
|
||||
# Download timeout in seconds (prevents indefinite hangs)
|
||||
DOWNLOAD_TIMEOUT_SECONDS = 120
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_utils import PathUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -119,115 +118,120 @@ class TaploServer(SolidLanguageServer):
|
||||
Creates a TaploServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
taplo_executable_path = self._setup_runtime_dependencies(solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=f"{taplo_executable_path} lsp stdio", cwd=repository_root_path),
|
||||
None,
|
||||
"toml",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Taplo and return the command to start the server.
|
||||
"""
|
||||
# First check if taplo is already installed system-wide
|
||||
system_taplo = shutil.which("taplo")
|
||||
if system_taplo:
|
||||
log.info(f"Using system-installed Taplo at: {system_taplo}")
|
||||
return system_taplo
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
# Setup local installation directory
|
||||
taplo_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "taplo")
|
||||
os.makedirs(taplo_dir, exist_ok=True)
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Taplo and return the command to start the server.
|
||||
"""
|
||||
# First check if taplo is already installed system-wide
|
||||
system_taplo = shutil.which("taplo")
|
||||
if system_taplo:
|
||||
log.info(f"Using system-installed Taplo at: {system_taplo}")
|
||||
return system_taplo
|
||||
|
||||
_, executable_name = _get_taplo_download_url()
|
||||
taplo_executable = os.path.join(taplo_dir, executable_name)
|
||||
# Setup local installation directory
|
||||
taplo_dir = os.path.join(self._ls_resources_dir, "taplo")
|
||||
os.makedirs(taplo_dir, exist_ok=True)
|
||||
|
||||
if os.path.exists(taplo_executable) and os.access(taplo_executable, os.X_OK):
|
||||
log.info(f"Using cached Taplo at: {taplo_executable}")
|
||||
return taplo_executable
|
||||
_, executable_name = _get_taplo_download_url()
|
||||
taplo_executable = os.path.join(taplo_dir, executable_name)
|
||||
|
||||
# Download and install Taplo
|
||||
log.info(f"Taplo not found. Downloading version {TAPLO_VERSION}...")
|
||||
cls._download_taplo(taplo_dir, taplo_executable)
|
||||
if os.path.exists(taplo_executable) and os.access(taplo_executable, os.X_OK):
|
||||
log.info(f"Using cached Taplo at: {taplo_executable}")
|
||||
return taplo_executable
|
||||
|
||||
if not os.path.exists(taplo_executable):
|
||||
raise FileNotFoundError(
|
||||
f"Taplo executable not found at {taplo_executable}. "
|
||||
"Installation may have failed. Try installing manually: cargo install taplo-cli --locked"
|
||||
)
|
||||
# Download and install Taplo
|
||||
log.info(f"Taplo not found. Downloading version {TAPLO_VERSION}...")
|
||||
self._download_taplo(taplo_dir, taplo_executable)
|
||||
|
||||
return taplo_executable
|
||||
|
||||
@classmethod
|
||||
def _download_taplo(cls, install_dir: str, executable_path: str) -> None:
|
||||
"""Download and extract Taplo binary with SHA256 verification."""
|
||||
# TODO: consider using existing download utilities in SolidLSP instead of the custom logic here
|
||||
download_url, _ = _get_taplo_download_url()
|
||||
archive_filename = os.path.basename(download_url)
|
||||
|
||||
try:
|
||||
log.info(f"Downloading Taplo from: {download_url}")
|
||||
archive_path = os.path.join(install_dir, archive_filename)
|
||||
|
||||
# Download the archive with timeout to prevent indefinite hangs
|
||||
old_timeout = socket.getdefaulttimeout()
|
||||
try:
|
||||
socket.setdefaulttimeout(DOWNLOAD_TIMEOUT_SECONDS)
|
||||
urllib.request.urlretrieve(download_url, archive_path)
|
||||
finally:
|
||||
socket.setdefaulttimeout(old_timeout)
|
||||
|
||||
# Verify SHA256 checksum
|
||||
expected_hash = TAPLO_SHA256_CHECKSUMS.get(archive_filename)
|
||||
if expected_hash:
|
||||
if not _verify_sha256(archive_path, expected_hash):
|
||||
os.remove(archive_path)
|
||||
raise RuntimeError(
|
||||
f"SHA256 checksum verification failed for {archive_filename}. "
|
||||
"The downloaded file may be corrupted or tampered with. "
|
||||
"Try installing manually: cargo install taplo-cli --locked"
|
||||
)
|
||||
log.info(f"SHA256 checksum verified for {archive_filename}")
|
||||
else:
|
||||
log.warning(
|
||||
f"No SHA256 checksum available for {archive_filename}. "
|
||||
"Skipping verification - consider installing manually: cargo install taplo-cli --locked"
|
||||
if not os.path.exists(taplo_executable):
|
||||
raise FileNotFoundError(
|
||||
f"Taplo executable not found at {taplo_executable}. "
|
||||
"Installation may have failed. Try installing manually: cargo install taplo-cli --locked"
|
||||
)
|
||||
|
||||
# Extract based on format
|
||||
if archive_path.endswith(".gz") and not archive_path.endswith(".tar.gz"):
|
||||
# Single file gzip
|
||||
with gzip.open(archive_path, "rb") as f_in:
|
||||
with open(executable_path, "wb") as f_out:
|
||||
f_out.write(f_in.read())
|
||||
elif archive_path.endswith(".zip"):
|
||||
import zipfile
|
||||
return taplo_executable
|
||||
|
||||
with zipfile.ZipFile(archive_path, "r") as zip_ref:
|
||||
# Security: Validate paths to prevent zip slip vulnerability
|
||||
for member in zip_ref.namelist():
|
||||
member_path = os.path.normpath(os.path.join(install_dir, member))
|
||||
if not member_path.startswith(os.path.normpath(install_dir)):
|
||||
raise RuntimeError(f"Zip slip detected: {member} attempts to escape install directory")
|
||||
zip_ref.extractall(install_dir)
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path, "lsp", "stdio"]
|
||||
|
||||
# Make executable on Unix systems
|
||||
if os.name != "nt":
|
||||
os.chmod(executable_path, os.stat(executable_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
@classmethod
|
||||
def _download_taplo(cls, install_dir: str, executable_path: str) -> None:
|
||||
"""Download and extract Taplo binary with SHA256 verification."""
|
||||
# TODO: consider using existing download utilities in SolidLSP instead of the custom logic here
|
||||
download_url, _ = _get_taplo_download_url()
|
||||
archive_filename = os.path.basename(download_url)
|
||||
|
||||
# Clean up archive
|
||||
os.remove(archive_path)
|
||||
log.info(f"Taplo installed successfully at: {executable_path}")
|
||||
try:
|
||||
log.info(f"Downloading Taplo from: {download_url}")
|
||||
archive_path = os.path.join(install_dir, archive_filename)
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Failed to download Taplo: {e}")
|
||||
raise RuntimeError(
|
||||
f"Failed to download Taplo from {download_url}. Try installing manually: cargo install taplo-cli --locked"
|
||||
) from e
|
||||
# Download the archive with timeout to prevent indefinite hangs
|
||||
old_timeout = socket.getdefaulttimeout()
|
||||
try:
|
||||
socket.setdefaulttimeout(DOWNLOAD_TIMEOUT_SECONDS)
|
||||
urllib.request.urlretrieve(download_url, archive_path)
|
||||
finally:
|
||||
socket.setdefaulttimeout(old_timeout)
|
||||
|
||||
# Verify SHA256 checksum
|
||||
expected_hash = TAPLO_SHA256_CHECKSUMS.get(archive_filename)
|
||||
if expected_hash:
|
||||
if not _verify_sha256(archive_path, expected_hash):
|
||||
os.remove(archive_path)
|
||||
raise RuntimeError(
|
||||
f"SHA256 checksum verification failed for {archive_filename}. "
|
||||
"The downloaded file may be corrupted or tampered with. "
|
||||
"Try installing manually: cargo install taplo-cli --locked"
|
||||
)
|
||||
log.info(f"SHA256 checksum verified for {archive_filename}")
|
||||
else:
|
||||
log.warning(
|
||||
f"No SHA256 checksum available for {archive_filename}. "
|
||||
"Skipping verification - consider installing manually: cargo install taplo-cli --locked"
|
||||
)
|
||||
|
||||
# Extract based on format
|
||||
if archive_path.endswith(".gz") and not archive_path.endswith(".tar.gz"):
|
||||
# Single file gzip
|
||||
with gzip.open(archive_path, "rb") as f_in:
|
||||
with open(executable_path, "wb") as f_out:
|
||||
f_out.write(f_in.read())
|
||||
elif archive_path.endswith(".zip"):
|
||||
import zipfile
|
||||
|
||||
with zipfile.ZipFile(archive_path, "r") as zip_ref:
|
||||
# Security: Validate paths to prevent zip slip vulnerability
|
||||
for member in zip_ref.namelist():
|
||||
member_path = os.path.normpath(os.path.join(install_dir, member))
|
||||
if not member_path.startswith(os.path.normpath(install_dir)):
|
||||
raise RuntimeError(f"Zip slip detected: {member} attempts to escape install directory")
|
||||
zip_ref.extractall(install_dir)
|
||||
|
||||
# Make executable on Unix systems
|
||||
if os.name != "nt":
|
||||
os.chmod(executable_path, os.stat(executable_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
# Clean up archive
|
||||
os.remove(archive_path)
|
||||
log.info(f"Taplo installed successfully at: {executable_path}")
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Failed to download Taplo: {e}")
|
||||
raise RuntimeError(
|
||||
f"Failed to download Taplo from {download_url}. Try installing manually: cargo install taplo-cli --locked"
|
||||
) from e
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
|
||||
@@ -10,10 +10,9 @@ import shutil
|
||||
from typing import Any
|
||||
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
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__)
|
||||
@@ -46,55 +45,61 @@ class YamlLanguageServer(SolidLanguageServer):
|
||||
Creates a YamlLanguageServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
yaml_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=yaml_lsp_executable_path, cwd=repository_root_path),
|
||||
None,
|
||||
"yaml",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for YAML Language Server and return the command to start the server.
|
||||
"""
|
||||
# Verify both node and npm are installed
|
||||
is_node_installed = shutil.which("node") is not None
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="yaml-language-server",
|
||||
description="yaml-language-server package (Red Hat)",
|
||||
command="npm install --prefix ./ yaml-language-server@1.19.2",
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
)
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for YAML Language Server and return the command to start the server.
|
||||
"""
|
||||
# Verify both node and npm are installed
|
||||
is_node_installed = shutil.which("node") is not None
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which("npm") is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
|
||||
# Install yaml-language-server if not already installed
|
||||
yaml_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "yaml-lsp")
|
||||
yaml_executable_path = os.path.join(yaml_ls_dir, "node_modules", ".bin", "yaml-language-server")
|
||||
|
||||
# Handle Windows executable extension
|
||||
if os.name == "nt":
|
||||
yaml_executable_path += ".cmd"
|
||||
|
||||
if not os.path.exists(yaml_executable_path):
|
||||
log.info(f"YAML Language Server executable not found at {yaml_executable_path}. Installing...")
|
||||
deps.install(yaml_ls_dir)
|
||||
log.info("YAML language server dependencies installed successfully")
|
||||
|
||||
if not os.path.exists(yaml_executable_path):
|
||||
raise FileNotFoundError(
|
||||
f"yaml-language-server executable not found at {yaml_executable_path}, something went wrong with the installation."
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="yaml-language-server",
|
||||
description="yaml-language-server package (Red Hat)",
|
||||
command="npm install --prefix ./ yaml-language-server@1.19.2",
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
)
|
||||
return f"{yaml_executable_path} --stdio"
|
||||
|
||||
# Install yaml-language-server if not already installed
|
||||
yaml_ls_dir = os.path.join(self._ls_resources_dir, "yaml-lsp")
|
||||
yaml_executable_path = os.path.join(yaml_ls_dir, "node_modules", ".bin", "yaml-language-server")
|
||||
|
||||
# Handle Windows executable extension
|
||||
if os.name == "nt":
|
||||
yaml_executable_path += ".cmd"
|
||||
|
||||
if not os.path.exists(yaml_executable_path):
|
||||
log.info(f"YAML Language Server executable not found at {yaml_executable_path}. Installing...")
|
||||
deps.install(yaml_ls_dir)
|
||||
log.info("YAML language server dependencies installed successfully")
|
||||
|
||||
if not os.path.exists(yaml_executable_path):
|
||||
raise FileNotFoundError(
|
||||
f"yaml-language-server executable not found at {yaml_executable_path}, something went wrong with the installation."
|
||||
)
|
||||
|
||||
return yaml_executable_path
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path, "--stdio"]
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
|
||||
+16
-10
@@ -18,6 +18,7 @@ from typing import Self, Union, cast
|
||||
|
||||
import pathspec
|
||||
from sensai.util.pickle import getstate, load_pickle
|
||||
from sensai.util.string import ToStringMixin
|
||||
|
||||
from serena.text_utils import MatchedConsecutiveLines
|
||||
from serena.util.file_system import match_path
|
||||
@@ -130,7 +131,7 @@ class LSPFileBuffer:
|
||||
return self.contents.split("\n")
|
||||
|
||||
|
||||
class SymbolBody:
|
||||
class SymbolBody(ToStringMixin):
|
||||
"""
|
||||
Representation of the body of a symbol, which allows the extraction of the symbol's text
|
||||
from the lines of the file it is defined in.
|
||||
@@ -147,14 +148,21 @@ class SymbolBody:
|
||||
self._end_line = end_line
|
||||
self._end_col = end_col
|
||||
|
||||
def _tostring_excludes(self) -> list[str]:
|
||||
return ["_lines"]
|
||||
|
||||
def get_text(self) -> str:
|
||||
# extract relevant lines
|
||||
symbol_body = "\n".join(self._lines[self._start_line : self._end_line + 1])
|
||||
|
||||
# remove leading indentation
|
||||
# remove leading content from the first line
|
||||
symbol_body = symbol_body[self._start_col :]
|
||||
|
||||
# TODO: handle end_col properly (this was never implemented)
|
||||
# remove trailing content from the last line
|
||||
last_line = self._lines[self._end_line]
|
||||
trailing_length = len(last_line) - self._end_col
|
||||
if trailing_length > 0:
|
||||
symbol_body = symbol_body[: -(len(last_line) - self._end_col)]
|
||||
|
||||
return symbol_body
|
||||
|
||||
@@ -233,13 +241,12 @@ class LanguageServerDependencyProvider(ABC):
|
||||
self._ls_resources_dir = ls_resources_dir
|
||||
|
||||
@abstractmethod
|
||||
def create_launch_command(self) -> list[str] | str:
|
||||
def create_launch_command(self) -> list[str]:
|
||||
"""
|
||||
Creates the launch command for this language server, potentially downloading and installing dependencies
|
||||
beforehand.
|
||||
|
||||
:return: the launch command as a list containing the executable and its arguments (preferred for robustness)
|
||||
or the entire command in a single string.
|
||||
:return: the launch command as a list containing the executable and its arguments
|
||||
"""
|
||||
|
||||
def create_launch_command_env(self) -> dict[str, str]:
|
||||
@@ -271,7 +278,7 @@ class LanguageServerDependencyProviderSinglePath(LanguageServerDependencyProvide
|
||||
:return: the core dependency's path (e.g. executable, jar, etc.)
|
||||
"""
|
||||
|
||||
def create_launch_command(self) -> Union[str, list[str]]:
|
||||
def create_launch_command(self) -> list[str]:
|
||||
path = self._custom_settings.get("ls_path", None)
|
||||
if path is not None:
|
||||
core_path = path
|
||||
@@ -280,11 +287,10 @@ class LanguageServerDependencyProviderSinglePath(LanguageServerDependencyProvide
|
||||
return self._create_launch_command(core_path)
|
||||
|
||||
@abstractmethod
|
||||
def _create_launch_command(self, core_path: str) -> list[str] | str:
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
"""
|
||||
:param core_path: path to the core dependency
|
||||
:return: the launch command as a list containing the executable and its arguments (preferred for robustness)
|
||||
or the entire command in a single string.
|
||||
:return: the launch command as a list containing the executable and its arguments
|
||||
"""
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user