mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-20 00:15:41 +00:00
Elixir: refactored to be in line with the other LS implementations
This commit is contained in:
1 parent
b63acf2dbe
commit
debcccbee2
3 files changed
+111
-197
No files matched your search
@@ -1,4 +1,3 @@
|
|||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
@@ -6,17 +5,18 @@ import stat
|
|||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from pathlib import PurePath
|
|
||||||
|
|
||||||
from overrides import override
|
from overrides import override
|
||||||
|
|
||||||
from solidlsp.ls import SolidLanguageServer
|
from solidlsp.ls import SolidLanguageServer
|
||||||
from solidlsp.ls_config import LanguageServerConfig
|
from solidlsp.ls_config import LanguageServerConfig
|
||||||
from solidlsp.ls_logger import LanguageServerLogger
|
from solidlsp.ls_logger import LanguageServerLogger
|
||||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
|
||||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||||
|
|
||||||
|
from ..common import RuntimeDependency
|
||||||
|
|
||||||
|
|
||||||
class ElixirTools(SolidLanguageServer):
|
class ElixirTools(SolidLanguageServer):
|
||||||
"""
|
"""
|
||||||
@@ -68,8 +68,8 @@ class ElixirTools(SolidLanguageServer):
|
|||||||
|
|
||||||
return filtered_response
|
return filtered_response
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def _get_elixir_version():
|
def _get_elixir_version(cls):
|
||||||
"""Get the installed Elixir version or None if not found."""
|
"""Get the installed Elixir version or None if not found."""
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(["elixir", "--version"], capture_output=True, text=True, check=False)
|
result = subprocess.run(["elixir", "--version"], capture_output=True, text=True, check=False)
|
||||||
@@ -79,13 +79,14 @@ class ElixirTools(SolidLanguageServer):
|
|||||||
return None
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def setupRuntimeDependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
@classmethod
|
||||||
|
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||||
"""
|
"""
|
||||||
Setup runtime dependencies for Next LS.
|
Setup runtime dependencies for Next LS.
|
||||||
Downloads the Next LS binary for the current platform and returns the path to the executable.
|
Downloads the Next LS binary for the current platform and returns the path to the executable.
|
||||||
"""
|
"""
|
||||||
# Check if Elixir is available first
|
# Check if Elixir is available first
|
||||||
elixir_version = self._get_elixir_version()
|
elixir_version = cls._get_elixir_version()
|
||||||
if not elixir_version:
|
if not elixir_version:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Elixir is not installed. Please install Elixir from https://elixir-lang.org/install.html and make sure it is added to your PATH."
|
"Elixir is not installed. Please install Elixir from https://elixir-lang.org/install.html and make sure it is added to your PATH."
|
||||||
@@ -93,62 +94,68 @@ class ElixirTools(SolidLanguageServer):
|
|||||||
|
|
||||||
logger.log(f"Found Elixir: {elixir_version}", logging.INFO)
|
logger.log(f"Found Elixir: {elixir_version}", logging.INFO)
|
||||||
|
|
||||||
platformId = PlatformUtils.get_platform_id()
|
platform_id = PlatformUtils.get_platform_id()
|
||||||
|
|
||||||
with open(str(PurePath(os.path.dirname(__file__), "runtime_dependencies.json")), encoding="utf-8") as f:
|
|
||||||
runtimeDependencies = json.load(f)
|
|
||||||
del runtimeDependencies["_description"]
|
|
||||||
|
|
||||||
os.makedirs(str(PurePath(os.path.abspath(os.path.dirname(__file__)), "static")), exist_ok=True)
|
|
||||||
|
|
||||||
# Map platform IDs to runtime dependency keys
|
|
||||||
platform_mapping = {
|
|
||||||
"linux-x64": "linux-x64",
|
|
||||||
"osx-x64": "darwin-x64",
|
|
||||||
"osx-arm64": "darwin-arm64",
|
|
||||||
"darwin-x64": "darwin-x64",
|
|
||||||
"darwin-arm64": "darwin-arm64",
|
|
||||||
"win-x64": "win-x64",
|
|
||||||
}
|
|
||||||
|
|
||||||
platform_key = platform_mapping.get(platformId.value)
|
|
||||||
if not platform_key:
|
|
||||||
raise RuntimeError(f"Unsupported platform for Next LS: {platformId.value}")
|
|
||||||
|
|
||||||
# Check for Windows and provide a helpful error message
|
# Check for Windows and provide a helpful error message
|
||||||
if platformId.value.startswith("win"):
|
if platform_id.value.startswith("win"):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Windows is not supported by Next LS. The Next LS project does not provide Windows binaries. "
|
"Windows is not supported by Next LS. The Next LS project does not provide Windows binaries. "
|
||||||
"Consider using Windows Subsystem for Linux (WSL) or a virtual machine with Linux/macOS."
|
"Consider using Windows Subsystem for Linux (WSL) or a virtual machine with Linux/macOS."
|
||||||
)
|
)
|
||||||
|
|
||||||
dependency = runtimeDependencies["next_ls"][platform_key]
|
valid_platforms = [
|
||||||
next_ls_dir = str(PurePath(os.path.abspath(os.path.dirname(__file__)), "static", dependency["relative_extraction_path"]))
|
PlatformId.LINUX_x64,
|
||||||
os.makedirs(next_ls_dir, exist_ok=True)
|
PlatformId.OSX_x64,
|
||||||
|
PlatformId.OSX_arm64,
|
||||||
|
]
|
||||||
|
assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for Next LS at the moment"
|
||||||
|
|
||||||
executable_path = str(PurePath(next_ls_dir, dependency["executable_name"]))
|
next_ls_dir = os.path.join(cls.ls_resources_dir(), "next-ls")
|
||||||
binary_path = str(PurePath(next_ls_dir, dependency["binary_name"]))
|
|
||||||
|
# Define runtime dependencies inline
|
||||||
|
runtime_deps = {
|
||||||
|
PlatformId.LINUX_x64: RuntimeDependency(
|
||||||
|
id="next_ls_linux_amd64",
|
||||||
|
platform_id="linux-x64",
|
||||||
|
url="https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_linux_amd64",
|
||||||
|
archive_type="binary",
|
||||||
|
binary_name="next_ls_linux_amd64",
|
||||||
|
extract_path="next_ls",
|
||||||
|
),
|
||||||
|
PlatformId.OSX_x64: RuntimeDependency(
|
||||||
|
id="next_ls_darwin_amd64",
|
||||||
|
platform_id="osx-x64",
|
||||||
|
url="https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_darwin_amd64",
|
||||||
|
archive_type="binary",
|
||||||
|
binary_name="next_ls_darwin_amd64",
|
||||||
|
extract_path="next_ls",
|
||||||
|
),
|
||||||
|
PlatformId.OSX_arm64: RuntimeDependency(
|
||||||
|
id="next_ls_darwin_arm64",
|
||||||
|
platform_id="osx-arm64",
|
||||||
|
url="https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_darwin_arm64",
|
||||||
|
archive_type="binary",
|
||||||
|
binary_name="next_ls_darwin_arm64",
|
||||||
|
extract_path="next_ls",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
dependency = runtime_deps[platform_id]
|
||||||
|
executable_path = os.path.join(next_ls_dir, "nextls")
|
||||||
|
binary_path = os.path.join(next_ls_dir, dependency.binary_name)
|
||||||
|
|
||||||
if not os.path.exists(executable_path):
|
if not os.path.exists(executable_path):
|
||||||
logger.log(f"Downloading Next LS binary from {dependency['url']}", logging.INFO)
|
logger.log(f"Downloading Next LS binary from {dependency.url}", logging.INFO)
|
||||||
FileUtils.download_file(logger, dependency["url"], binary_path)
|
FileUtils.download_file(logger, dependency.url, binary_path)
|
||||||
|
|
||||||
# Make the binary executable on Unix-like systems
|
# Make the binary executable on Unix-like systems
|
||||||
if not platformId.value.startswith("win"):
|
os.chmod(binary_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
||||||
os.chmod(binary_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
|
||||||
|
|
||||||
# Create a symlink or copy with the expected name
|
# Create a symlink with the expected name
|
||||||
if binary_path != executable_path:
|
if binary_path != executable_path:
|
||||||
if os.path.exists(executable_path):
|
if os.path.exists(executable_path):
|
||||||
os.remove(executable_path)
|
os.remove(executable_path)
|
||||||
if platformId.value.startswith("win"):
|
os.symlink(os.path.basename(binary_path), executable_path)
|
||||||
# On Windows, copy the file
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
shutil.copy2(binary_path, executable_path)
|
|
||||||
else:
|
|
||||||
# On Unix-like systems, create a symlink
|
|
||||||
os.symlink(os.path.basename(binary_path), executable_path)
|
|
||||||
|
|
||||||
assert os.path.exists(executable_path), f"Next LS executable not found at {executable_path}"
|
assert os.path.exists(executable_path), f"Next LS executable not found at {executable_path}"
|
||||||
|
|
||||||
@@ -156,7 +163,7 @@ class ElixirTools(SolidLanguageServer):
|
|||||||
return executable_path
|
return executable_path
|
||||||
|
|
||||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||||
nextls_executable_path = self.setupRuntimeDependencies(logger, config)
|
nextls_executable_path = self._setup_runtime_dependencies(logger, config)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
config,
|
config,
|
||||||
@@ -171,29 +178,66 @@ class ElixirTools(SolidLanguageServer):
|
|||||||
# Set generous timeout for Next LS which can be slow to initialize and respond
|
# Set generous timeout for Next LS which can be slow to initialize and respond
|
||||||
self.set_request_timeout(180.0) # 60 seconds for all environments
|
self.set_request_timeout(180.0) # 60 seconds for all environments
|
||||||
|
|
||||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
@staticmethod
|
||||||
|
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||||
"""
|
"""
|
||||||
Returns the initialize params for the Next LS Language Server.
|
Returns the initialize params for the Next LS Language Server.
|
||||||
"""
|
"""
|
||||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f:
|
root_uri = pathlib.Path(repository_absolute_path).as_uri()
|
||||||
d = json.load(f)
|
initialize_params = {
|
||||||
|
"processId": os.getpid(),
|
||||||
|
"locale": "en",
|
||||||
|
"rootPath": repository_absolute_path,
|
||||||
|
"rootUri": root_uri,
|
||||||
|
"initializationOptions": {
|
||||||
|
"mix_env": "dev",
|
||||||
|
"mix_target": "host",
|
||||||
|
"experimental": {"completions": {"enable": False}},
|
||||||
|
"extensions": {"credo": {"enable": True, "cli_options": []}},
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"textDocument": {
|
||||||
|
"synchronization": {"didSave": True, "dynamicRegistration": True},
|
||||||
|
"completion": {
|
||||||
|
"dynamicRegistration": True,
|
||||||
|
"completionItem": {"snippetSupport": True, "documentationFormat": ["markdown", "plaintext"]},
|
||||||
|
},
|
||||||
|
"definition": {"dynamicRegistration": True},
|
||||||
|
"references": {"dynamicRegistration": True},
|
||||||
|
"documentSymbol": {
|
||||||
|
"dynamicRegistration": True,
|
||||||
|
"hierarchicalDocumentSymbolSupport": True,
|
||||||
|
"symbolKind": {"valueSet": list(range(1, 27))},
|
||||||
|
},
|
||||||
|
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
|
||||||
|
"formatting": {"dynamicRegistration": True},
|
||||||
|
"codeAction": {
|
||||||
|
"dynamicRegistration": True,
|
||||||
|
"codeActionLiteralSupport": {
|
||||||
|
"codeActionKind": {
|
||||||
|
"valueSet": [
|
||||||
|
"quickfix",
|
||||||
|
"refactor",
|
||||||
|
"refactor.extract",
|
||||||
|
"refactor.inline",
|
||||||
|
"refactor.rewrite",
|
||||||
|
"source",
|
||||||
|
"source.organizeImports",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"workspaceFolders": True,
|
||||||
|
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||||
|
"executeCommand": {"dynamicRegistration": True},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
|
||||||
|
}
|
||||||
|
|
||||||
del d["_description"]
|
return initialize_params
|
||||||
|
|
||||||
d["processId"] = os.getpid()
|
|
||||||
assert d["rootPath"] == "$rootPath"
|
|
||||||
d["rootPath"] = repository_absolute_path
|
|
||||||
|
|
||||||
assert d["rootUri"] == "$rootUri"
|
|
||||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
|
||||||
|
|
||||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
|
||||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
|
||||||
|
|
||||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
|
||||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
def _start_server(self):
|
def _start_server(self):
|
||||||
"""Start Next LS server process"""
|
"""Start Next LS server process"""
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
{
|
|
||||||
"_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",
|
|
||||||
"processId": "os.getpid()",
|
|
||||||
"locale": "en",
|
|
||||||
"rootPath": "$rootPath",
|
|
||||||
"rootUri": "$rootUri",
|
|
||||||
"initializationOptions": {
|
|
||||||
"mix_env": "dev",
|
|
||||||
"mix_target": "host",
|
|
||||||
"experimental": {
|
|
||||||
"completions": {
|
|
||||||
"enable": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extensions": {
|
|
||||||
"credo": {
|
|
||||||
"enable": true,
|
|
||||||
"cli_options": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"capabilities": {
|
|
||||||
"textDocument": {
|
|
||||||
"synchronization": {
|
|
||||||
"didSave": true,
|
|
||||||
"dynamicRegistration": true
|
|
||||||
},
|
|
||||||
"completion": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"completionItem": {
|
|
||||||
"snippetSupport": true,
|
|
||||||
"documentationFormat": [
|
|
||||||
"markdown",
|
|
||||||
"plaintext"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"definition": {
|
|
||||||
"dynamicRegistration": true
|
|
||||||
},
|
|
||||||
"references": {
|
|
||||||
"dynamicRegistration": true
|
|
||||||
},
|
|
||||||
"documentSymbol": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"hierarchicalDocumentSymbolSupport": true,
|
|
||||||
"symbolKind": {
|
|
||||||
"valueSet": [
|
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
|
||||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hover": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"contentFormat": [
|
|
||||||
"markdown",
|
|
||||||
"plaintext"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"formatting": {
|
|
||||||
"dynamicRegistration": true
|
|
||||||
},
|
|
||||||
"codeAction": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"codeActionLiteralSupport": {
|
|
||||||
"codeActionKind": {
|
|
||||||
"valueSet": [
|
|
||||||
"quickfix",
|
|
||||||
"refactor",
|
|
||||||
"refactor.extract",
|
|
||||||
"refactor.inline",
|
|
||||||
"refactor.rewrite",
|
|
||||||
"source",
|
|
||||||
"source.organizeImports"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"workspace": {
|
|
||||||
"workspaceFolders": true,
|
|
||||||
"didChangeConfiguration": {
|
|
||||||
"dynamicRegistration": true
|
|
||||||
},
|
|
||||||
"executeCommand": {
|
|
||||||
"dynamicRegistration": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"workspaceFolders": [
|
|
||||||
{
|
|
||||||
"uri": "$uri",
|
|
||||||
"name": "$name"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"_description": "This file lists the runtime dependencies for the Elixir Language Server (Next LS)",
|
|
||||||
"next_ls": {
|
|
||||||
"linux-x64": {
|
|
||||||
"url": "https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_linux_amd64",
|
|
||||||
"archiveType": "binary",
|
|
||||||
"relative_extraction_path": "next_ls",
|
|
||||||
"binary_name": "next_ls_linux_amd64",
|
|
||||||
"executable_name": "nextls"
|
|
||||||
},
|
|
||||||
"darwin-x64": {
|
|
||||||
"url": "https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_darwin_amd64",
|
|
||||||
"archiveType": "binary",
|
|
||||||
"relative_extraction_path": "next_ls",
|
|
||||||
"binary_name": "next_ls_darwin_amd64",
|
|
||||||
"executable_name": "nextls"
|
|
||||||
},
|
|
||||||
"darwin-arm64": {
|
|
||||||
"url": "https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_darwin_arm64",
|
|
||||||
"archiveType": "binary",
|
|
||||||
"relative_extraction_path": "next_ls",
|
|
||||||
"binary_name": "next_ls_darwin_arm64",
|
|
||||||
"executable_name": "nextls"
|
|
||||||
},
|
|
||||||
"win-x64": {
|
|
||||||
"url": "https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_windows_amd64.exe",
|
|
||||||
"archiveType": "binary",
|
|
||||||
"relative_extraction_path": "next_ls",
|
|
||||||
"binary_name": "next_ls_windows_amd64.exe",
|
|
||||||
"executable_name": "nextls.exe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user