Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
431 changes: 431 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)}"