Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyC
"osx-arm64",
], "Unsupported platform: " + platform_id.value

# Check for custom binary after platform validation
if config.custom_lsp_binary:
path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(path)
return path

runtime_dependencies = d["runtimeDependencies"]
runtime_dependencies = [
dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(self, config, logger, repository_root_path):
Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""

executable_path = self.setup_runtime_dependencies(logger)
executable_path = self.setup_runtime_dependencies(logger, config)
super().__init__(
config,
logger,
Expand All @@ -30,7 +30,20 @@ def __init__(self, config, logger, repository_root_path):
"dart",
)

def setup_runtime_dependencies(self, logger: "MultilspyLogger") -> str:
def setup_runtime_dependencies(self, logger: "MultilspyLogger", config: "MultilspyConfig") -> str:
lsp_args = [
"language-server",
"--client-id",
"multilspy.dart",
"--client-version",
"1.2",
]
# Check for custom binary after platform validation
if config.custom_lsp_binary:
path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(path)
return f"{path} {' '.join(lsp_args)}"

platform_id = PlatformUtils.get_platform_id()

with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f:
Expand Down Expand Up @@ -58,7 +71,7 @@ def setup_runtime_dependencies(self, logger: "MultilspyLogger") -> str:
assert os.path.exists(dart_executable_path)
os.chmod(dart_executable_path, stat.S_IEXEC)

return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2"
return f"{dart_executable_path} {' '.join(lsp_args)}"


def _get_initialize_params(self, repository_absolute_path: str):
Expand Down
12 changes: 10 additions & 2 deletions src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_
Creates a new EclipseJDTLS instance initializing the language server settings appropriately.
This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""

runtime_dependency_paths = self.setupRuntimeDependencies(logger, config)
self.runtime_dependency_paths = runtime_dependency_paths

Expand Down Expand Up @@ -97,6 +97,14 @@ def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_
# TODO: Add "self.runtime_dependency_paths.jre_home_path"/bin to $PATH as well
proc_env = {"syntaxserver": "false", "JAVA_HOME": self.runtime_dependency_paths.jre_home_path}
proc_cwd = repository_root_path

# Override JDTLS launcher jar path if custom "binary" is provided
launcher_jar = jdtls_launcher_jar
if config.custom_lsp_binary:
custom_path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(custom_path)
launcher_jar = custom_path

cmd = " ".join(
[
jre_path,
Expand Down Expand Up @@ -125,7 +133,7 @@ def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_
f"-javaagent:{lombok_jar_path}",
f"-Djdt.core.sharedIndexLocation={shared_cache_location}",
"-jar",
jdtls_launcher_jar,
launcher_jar,
"-configuration",
jdtls_config_path,
"-data",
Expand Down
15 changes: 12 additions & 3 deletions src/multilspy/language_servers/gopls/gopls.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _get_gopls_version():
return None

@classmethod
def setup_runtime_dependency(cls):
def setup_runtime_dependency(cls, config: MultilspyConfig):
"""
Check if required Go runtime dependencies are available.
Raises RuntimeError with helpful message if dependencies are missing.
Expand All @@ -53,6 +53,9 @@ def setup_runtime_dependency(cls):
go_version = cls._get_go_version()
if not go_version:
missing_deps.append(("Go", "https://golang.org/doc/install"))

if config.custom_lsp_binary:
return True

# Check for gopls
gopls_version = cls._get_gopls_version()
Expand All @@ -69,13 +72,19 @@ def setup_runtime_dependency(cls):

def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
# Check runtime dependencies before initializing
self.setup_runtime_dependency()
self.setup_runtime_dependency(config)

cmd = "gopls"
if config.custom_lsp_binary:
path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(path)
cmd = path

super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd="gopls", cwd=repository_root_path),
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
"go",
)
self.server_ready = asyncio.Event()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@ def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_
"""
Creates a JediServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
cmd = "jedi-language-server"
if config.custom_lsp_binary:
path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(path)
cmd = path

super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd="jedi-language-server", cwd=repository_root_path),
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
"python",
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyC
else:
raise FileNotFoundError(f"Kotlin Language Server script not found at {kotlin_script}")

# Override executable path if custom binary is provided
if config.custom_lsp_binary:
custom_path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(custom_path)
kotlin_executable_path = custom_path

return KotlinRuntimeDependencyPaths(
java_path=java_path,
java_home_path=java_home_path,
Expand Down
7 changes: 7 additions & 0 deletions src/multilspy/language_servers/omnisharp/omnisharp.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyCon
)
assert os.path.exists(razor_omnisharp_dll_path)

# Override executable path if custom binary is provided
# Note: Only the OmniSharp executable path is replaced.
if config.custom_lsp_binary:
custom_path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(custom_path)
omnisharp_executable_path = custom_path

return omnisharp_executable_path, razor_omnisharp_dll_path

@asynccontextmanager
Expand Down
6 changes: 6 additions & 0 deletions src/multilspy/language_servers/rust_analyzer/rust_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyC
"""
Setup runtime dependencies for rust_analyzer.
"""

if config.custom_lsp_binary:
path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(path)
return path

platform_id = PlatformUtils.get_platform_id()

with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f:
Expand Down
5 changes: 5 additions & 0 deletions src/multilspy/language_servers/solargraph/solargraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyC
"""
Setup runtime dependencies for Solargraph.
"""
# Check for custom binary after Ruby validation
if config.custom_lsp_binary:
path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(path)
return path

with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f:
d = json.load(f)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyC
]
assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment"

# Check for custom binary after platform validation
# Note: The '--stdio' flag is automatically appended to the custom binary path,
# as it's required for LSP communication. Provide the path to the
# typescript-language-server executable.
if config.custom_lsp_binary:
path = os.path.abspath(config.custom_lsp_binary)
assert os.path.exists(path)
return f"{path} --stdio"

with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f:
d = json.load(f)
del d["_description"]
Expand Down
21 changes: 21 additions & 0 deletions src/multilspy/multilspy_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from enum import Enum
from dataclasses import dataclass
from typing import Optional

class Language(str, Enum):
"""
Expand Down Expand Up @@ -34,6 +35,26 @@ class MultilspyConfig:
trace_lsp_communication: bool = False
start_independent_lsp_process: bool = True

# Optional path to a custom LSP binary/executable to use instead of the default.
#
# For most language servers, this simply replaces the executable path. However, there
# are some exceptions:
#
# - Java (EclipseJDTLS): Replaces only the launcher jar path. All Java/JVM arguments,
# runtime setup, and configuration are preserved. The custom binary should be a path
# to a JDTLS launcher jar file.
#
# - Kotlin: Replaces only the Kotlin LSP executable path. All Java setup (JAVA_HOME, etc.)
# is still performed and preserved, as it's required for Kotlin to function.
#
# - C# (OmniSharp): Replaces only the OmniSharp executable path. All setup including
# RazorOmnisharp DLL download is still performed, and the DLL path is still available
# if needed by the custom binary.
#
# For all other language servers (Rust, Python, Go, Ruby, C++), the custom binary
# path directly replaces the default executable with no other modifications.
custom_lsp_binary: Optional[str] = None

@classmethod
def from_dict(cls, env: dict):
"""
Expand Down
Loading