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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Status of the `main` branch. Changes prior to the next official version change w

* Language support:

* **Add support for BSL (1C:Enterprise)** via bsl-language-server (automatically downloads platform-specific distribution from GitHub releases; includes bundled runtime, no Java required)
* **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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ that implement the language server protocol (LSP).
The underlying language servers are typically open-source projects (like Serena) or at least freely available for use.

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, 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.
AL, Bash, BSL (1C:Enterprise), 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 language servers require additional dependencies to be installed; see the [Language Support](https://oraios.github.io/serena/01-about/020_programming-languages.html) page for details.
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ markers = [
"powershell: language server running for PowerShell",
"slow: tests that require additional Expert instances and have long startup times (~60-90s each)",
"toml: language server running for TOML",
"bsl: language server running for BSL (1C:Enterprise)",
]

[tool.codespell]
Expand Down
538 changes: 538 additions & 0 deletions src/solidlsp/language_servers/bsl_language_server.py

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/solidlsp/ls_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class Language(str, Enum):
GROOVY = "groovy"
VUE = "vue"
POWERSHELL = "powershell"
BSL = "bsl"
# Experimental or deprecated Language Servers
TYPESCRIPT_VTS = "typescript_vts"
"""Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
Expand Down Expand Up @@ -225,6 +226,8 @@ def get_source_fn_matcher(self) -> FilenameMatcher:
return FilenameMatcher("*.ps1", "*.psm1", "*.psd1")
case self.GROOVY:
return FilenameMatcher("*.groovy", "*.gvy")
case self.BSL:
return FilenameMatcher("*.bsl", "*.os")
case _:
raise ValueError(f"Unhandled language: {self}")

Expand Down Expand Up @@ -390,6 +393,10 @@ def get_ls_class(self) -> type["SolidLanguageServer"]:
from solidlsp.language_servers.groovy_language_server import GroovyLanguageServer

return GroovyLanguageServer
case self.BSL:
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer

return BSLLanguageServer
case _:
raise ValueError(f"Unhandled language: {self}")

Expand Down
13 changes: 13 additions & 0 deletions test/resources/repos/bsl/test_repo/helper.bsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Helper module with utility functions

Function HelperFunction() Export
Return "Helper function was called.";
EndFunction

Function ProcessData(Data) Export
If Data = Undefined Then
Return "No data provided";
EndIf;

Return "Processed: " + Data;
EndFunction
22 changes: 22 additions & 0 deletions test/resources/repos/bsl/test_repo/main.bsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Main module for BSL Language Server tests
// This file contains a function that is called from other modules

Function Greet(Name) Export
Return "Hello, " + Name + "!";
EndFunction

Function Add(A, B) Export
Return A + B;
EndFunction

Procedure Main() Export
UserName = "BSL User";
Greeting = Greet(UserName);
Message(Greeting);

Result = Add(5, 3);
Message(Result);

HelperResult = HelperFunction();
Message(HelperResult);
EndProcedure
6 changes: 6 additions & 0 deletions test/resources/repos/bsl/test_repo/simple_var.bsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Simple variable test module

Procedure SimpleVariableTest() Export
LocalVar = "test";
Message(LocalVar);
EndProcedure
1 change: 1 addition & 0 deletions test/solidlsp/bsl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# BSL Language Server tests
70 changes: 70 additions & 0 deletions test/solidlsp/bsl/test_bsl_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from pathlib import Path

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language


@pytest.mark.bsl
class TestBSLLanguageServer:
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.BSL], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test that the language server starts and stops successfully."""
# The fixture already handles start and stop
assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()

@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.BSL], indirect=True)
def test_find_document_symbols(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test that the language server can find document symbols."""
# Get document symbols for main.bsl
main_bsl_path = str(repo_path / "main.bsl")
document_symbols = language_server.request_document_symbols(main_bsl_path)

# Should find symbols: Greet function, Add function, Main procedure
all_symbols, root_symbols = document_symbols.get_all_symbols_and_roots()
assert len(root_symbols) >= 1, f"Expected at least 1 root symbol but got {len(root_symbols)}"

# Check that expected symbol names are present
symbol_names = [s["name"] for s in all_symbols]
assert any("Greet" in name for name in symbol_names), f"Expected 'Greet' function in symbols, got: {symbol_names}"

@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.BSL], indirect=True)
def test_find_definition_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test that the language server can find definitions within a file."""
main_bsl_path = str(repo_path / "main.bsl")

# In main.bsl:
# Line 14 (0-indexed: 13): Greeting = Greet(UserName);
# Find definition of Greet function (defined on line 4, 0-indexed: 3)
# Position on "Greet" in the call (after "Greeting = ")
definition_location_list = language_server.request_definition(main_bsl_path, 13, 16)

assert definition_location_list, f"Expected non-empty definition_location_list but got {definition_location_list=}"
assert len(definition_location_list) >= 1
definition_location = definition_location_list[0]
assert definition_location["uri"].endswith("main.bsl")
# Definition of Greet function should be on line 4 (0-indexed: 3)
assert definition_location["range"]["start"]["line"] == 3

@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.BSL], indirect=True)
def test_find_references_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test that the language server can find references within a file."""
main_bsl_path = str(repo_path / "main.bsl")

# Find references for Greet function
# Greet is defined on line 4 (0-indexed: 3), character 9 (on the 'G')
references = language_server.request_references(main_bsl_path, 3, 9)

assert references, f"Expected non-empty references for Greet but got {references=}"
# Should find at least one reference (the call on line 14)
assert len(references) >= 1, f"Expected at least 1 reference for Greet but got {len(references)}"

# Verify that one of the references is in main.bsl
main_bsl_refs = [r for r in references if r["uri"].endswith("main.bsl")]
assert len(main_bsl_refs) >= 1, f"Expected at least 1 reference in main.bsl but got {len(main_bsl_refs)}"