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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,5 @@ src/multilspy/language_servers/clangd_language_server/static/
venv/

src/multilspy/language_servers/intelephense/static/
src/multilspy/language_servers/elixir_language_server/static/
src/multilspy/language_servers/elixir_language_server/static/
src/multilspy/language_servers/bsl_language_server/static/
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pip install multilspy
| kotlin | KotlinLanguageServer |
| php | Intelephense |
| cpp | clangd |
| bsl | [bsl-language-server](https://github.com/1c-syntax/bsl-language-server) (1C:Enterprise / OneScript — requires Java 17+) |


## Usage
Expand All @@ -60,7 +61,7 @@ from multilspy import SyncLanguageServer
from multilspy.multilspy_config import MultilspyConfig
from multilspy.multilspy_logger import MultilspyLogger
...
config = MultilspyConfig.from_dict({"code_language": "java"}) # Also supports "python", "rust", "csharp", "typescript", "javascript", "go", "dart", "ruby", "kotlin", "php"
config = MultilspyConfig.from_dict({"code_language": "java"}) # Also supports "python", "rust", "csharp", "typescript", "javascript", "go", "dart", "ruby", "kotlin", "php", "bsl"
logger = MultilspyLogger()
lsp = SyncLanguageServer.create(config, logger, "/abs/path/to/project/root/")
with lsp.start_server():
Expand Down
4 changes: 4 additions & 0 deletions src/multilspy/language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ def create(cls, config: MultilspyConfig, logger: MultilspyLogger, repository_roo
from multilspy.language_servers.elixir_language_server.elixir_language_server import ElixirLanguageServer

return ElixirLanguageServer(config, logger, repository_root_path)
elif config.code_language == Language.BSL:
from multilspy.language_servers.bsl_language_server.bsl_language_server import BSLLanguageServer

return BSLLanguageServer(config, logger, repository_root_path)

else:
logger.log(f"Language {config.code_language} is not supported", logging.ERROR)
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"""
Provides BSL-specific instantiation of the LanguageServer class.

BSL (Business Scripting Language) is the programming language of the
1C:Enterprise platform and OneScript. This adapter wraps the bsl-language-server
JAR from https://github.com/1c-syntax/bsl-language-server.

Prerequisites:
- Java 17+ must be available in PATH.
- The BSL Language Server JAR is downloaded on first use from GitHub releases.
"""

import asyncio
import hashlib
import json
import logging
import os
import pathlib
import shutil
from contextlib import asynccontextmanager
from typing import AsyncIterator

from multilspy.language_server import LanguageServer
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
from multilspy.multilspy_config import MultilspyConfig
from multilspy.multilspy_logger import MultilspyLogger
from multilspy.multilspy_utils import FileUtils


class BSLLanguageServer(LanguageServer):
"""
Provides BSL-specific instantiation of the LanguageServer class.
"""

def __init__(
self,
config: MultilspyConfig,
logger: MultilspyLogger,
repository_root_path: str,
):
"""
Creates a BSL Language Server instance. Do not instantiate directly;
use `LanguageServer.create` with `Language.BSL`.
"""
jar_path = self._ensure_jar(logger)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(
cmd=["java", "-jar", str(jar_path), "--lsp"],
cwd=repository_root_path,
),
"bsl",
)

def _ensure_jar(self, logger: MultilspyLogger) -> str:
"""Verify Java and the BSL LS JAR are available; download JAR if missing."""
if shutil.which("java") is None:
raise RuntimeError(
"Java 17+ is required to run BSL Language Server, "
"but `java` was not found in PATH. "
"Install a JDK 17+ from https://adoptium.net/ "
"and ensure `java` is on PATH."
)

with open(
os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"),
"r",
encoding="utf-8",
) as f:
deps = json.load(f)
dep = deps["runtimeDependency"]

server_dir = os.path.join(os.path.dirname(__file__), "static", dep["version"])
os.makedirs(server_dir, exist_ok=True)
jar_path = os.path.join(server_dir, dep["binaryName"])

if not os.path.exists(jar_path):
logger.log(
f"Downloading BSL Language Server {dep['version']} "
f"from {dep['url']}",
logging.INFO,
)
FileUtils.download_file(logger, dep["url"], jar_path)

expected_sha = dep.get("sha256") or ""
if expected_sha:
with open(jar_path, "rb") as jar_f:
actual_sha = hashlib.sha256(jar_f.read()).hexdigest()
if actual_sha != expected_sha:
os.remove(jar_path)
raise RuntimeError(
f"BSL Language Server JAR SHA256 mismatch: "
f"expected {expected_sha}, got {actual_sha}. "
f"Corrupted or tampered download; retry required."
)

return jar_path

def _get_initialize_params(self, repository_absolute_path: str) -> dict:
with open(
os.path.join(os.path.dirname(__file__), "initialize_params.json"),
"r",
encoding="utf-8",
) as f:
d = json.load(f)

del d["_description"]

root = pathlib.Path(repository_absolute_path)
d["processId"] = os.getpid()
d["rootPath"] = repository_absolute_path
d["rootUri"] = root.as_uri()
d["workspaceFolders"][0]["uri"] = root.as_uri()
d["workspaceFolders"][0]["name"] = root.name or "workspace"
return d

@asynccontextmanager
async def start_server(self) -> AsyncIterator["BSLLanguageServer"]:
async def do_nothing(params): # noqa: ARG001
return

async def log_message(msg):
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)

self.server.on_request("client/registerCapability", do_nothing)
self.server.on_notification("language/status", do_nothing)
self.server.on_notification("window/logMessage", log_message)
self.server.on_notification("window/showMessage", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.server.on_notification("$/progress", do_nothing)

async with super().start_server():
self.logger.log(
f"Starting BSL Language Server: {self.server.process_launch_info.cmd}",
logging.INFO,
)
await self.server.start()

if self.server.process.returncode is not None:
raise RuntimeError(
f"BSL Language Server failed to start "
f"(exit code {self.server.process.returncode}). "
f"Check Java version (17+ required)."
)

initialize_params = self._get_initialize_params(self.repository_root_path)

init_response = await asyncio.wait_for(
self.server.send.initialize(initialize_params),
timeout=120,
)
self.logger.log(
f"BSL LS capabilities: "
f"{list(init_response.get('capabilities', {}).keys())}",
logging.INFO,
)

self.server.notify.initialized({})
self.completions_available.set()

try:
yield self
finally:
try:
await self.server.shutdown()
except Exception as e:
self.logger.log(
f"BSL LS shutdown() raised: {e!r}", logging.WARNING
)
try:
await self.server.stop()
except Exception as e:
self.logger.log(
f"BSL LS stop() raised: {e!r}", logging.WARNING
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"_description": "Initialization parameters for the BSL Language Server (1C Enterprise / OneScript). See https://github.com/1c-syntax/bsl-language-server.",
"processId": "$processId",
"rootPath": "$rootPath",
"rootUri": "$rootUri",
"workspaceFolders": [
{
"uri": "$rootUri",
"name": "$rootName"
}
],
"capabilities": {
"workspace": {
"workspaceFolders": true,
"configuration": true,
"didChangeWatchedFiles": {
"dynamicRegistration": false
},
"workspaceSymbol": {}
},
"textDocument": {
"synchronization": {
"didSave": true
},
"documentSymbol": {
"hierarchicalDocumentSymbolSupport": true
},
"definition": {
"linkSupport": true
},
"references": {},
"rename": {
"prepareSupport": true
},
"hover": {
"contentFormat": ["markdown", "plaintext"]
},
"completion": {
"completionItem": {
"snippetSupport": true,
"documentationFormat": ["markdown", "plaintext"]
}
},
"signatureHelp": {},
"formatting": {},
"codeAction": {}
}
},
"initializationOptions": {},
"trace": "off"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"_description": "Used to download the runtime dependencies for BSL Language Server from https://github.com/1c-syntax/bsl-language-server. Java 17+ must be available in PATH.",
"runtimeDependency": {
"id": "BSLLanguageServer",
"description": "BSL Language Server (1C Enterprise / OneScript)",
"version": "0.22.0",
"url": "https://github.com/1c-syntax/bsl-language-server/releases/download/0.22.0/bsl-language-server-0.22.0.jar",
"archiveType": "jar",
"binaryName": "bsl-language-server.jar",
"sha256": "10835036c3b39eba9d1bbef65debc36ea99939a651165cd55ef86bf41c7e28ea"
}
}
1 change: 1 addition & 0 deletions src/multilspy/multilspy_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Language(str, Enum):
CPP = "cpp"
PHP = "php"
ELIXIR = "elixir"
BSL = "bsl"

def __str__(self) -> str:
return self.value
Expand Down
Loading
Loading