mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 20:15:20 +00:00
format
This commit is contained in:
1 parent
a636b98719
commit
d13e1744e8
3 files changed
+57
-69
No files matched your search
@@ -85,7 +85,7 @@ With Serena, we provide direct, out-of-the-box support for:
|
||||
* C/C++ (you may experience issues with finding references, we are working on it)
|
||||
* Zig (requires installation of ZLS - Zig Language Server)
|
||||
* C#
|
||||
* F# (requires .NET SDK 6.0+ and uses FsAutoComplete/Ionide LSP server)
|
||||
* F# (requires .NET SDK 8.0+ and uses FsAutoComplete/Ionide LSP server)
|
||||
* Ruby (by default, uses [ruby-lsp](https://github.com/Shopify/ruby-lsp), specify ruby_solargraph as your language to use the previous solargraph based implementation)
|
||||
* Swift
|
||||
* Kotlin (uses the pre-alpha [official kotlin LS](https://github.com/Kotlin/kotlin-lsp), some issues may appear)
|
||||
|
||||
@@ -78,6 +78,7 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
|
||||
# Verify dotnet version
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
result = subprocess.run([dotnet_exe, "--version"], capture_output=True, text=True, check=True)
|
||||
logger.log(f"Found .NET SDK version: {result.stdout.strip()}", logging.INFO)
|
||||
@@ -105,19 +106,20 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
|
||||
if not os.path.exists(fsautocomplete_path):
|
||||
logger.log(f"FsAutoComplete executable not found at {fsautocomplete_path}. Installing...", logging.INFO)
|
||||
|
||||
|
||||
# Ensure the directory exists
|
||||
os.makedirs(fsharp_ls_dir, exist_ok=True)
|
||||
|
||||
|
||||
# Install FsAutoComplete using dotnet tool install
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
[dotnet_exe, "tool", "install", "--tool-path", fsharp_ls_dir, "fsautocomplete"],
|
||||
cwd=fsharp_ls_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
check=True,
|
||||
)
|
||||
logger.log("FsAutoComplete installed successfully", logging.INFO)
|
||||
logger.log(f"Installation output: {result.stdout}", logging.DEBUG)
|
||||
@@ -138,7 +140,7 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
Returns the initialize params for the F# Language Server.
|
||||
"""
|
||||
root_uri = pathlib.Path(self.repository_root_path).as_uri()
|
||||
|
||||
|
||||
initialize_params = {
|
||||
"processId": os.getpid(),
|
||||
"rootPath": self.repository_root_path,
|
||||
@@ -236,33 +238,25 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
"automaticWorkspaceInit": True,
|
||||
"abstractClassStubGeneration": True,
|
||||
"abstractClassStubGenerationObjectIdentifier": "this",
|
||||
"abstractClassStubGenerationMethodBody": "failwith \"Not Implemented\"",
|
||||
"abstractClassStubGenerationMethodBody": 'failwith "Not Implemented"',
|
||||
"addFsiWatcher": False,
|
||||
"codeLenses": {
|
||||
"signature": {"enabled": True},
|
||||
"references": {"enabled": True}
|
||||
},
|
||||
"codeLenses": {"signature": {"enabled": True}, "references": {"enabled": True}},
|
||||
"disableInMemoryProjectReferences": False,
|
||||
"dotNetRoot": self._get_dotnet_root(),
|
||||
"enableMSBuildProjectGraph": False,
|
||||
"excludeProjectDirectories": ["paket-files"],
|
||||
"externalAutocomplete": False,
|
||||
"fsac": {
|
||||
"attachDebugger": False,
|
||||
"silencedLogs": [],
|
||||
"conserveMemory": False,
|
||||
"netCoreDllPath": ""
|
||||
},
|
||||
"fsac": {"attachDebugger": False, "silencedLogs": [], "conserveMemory": False, "netCoreDllPath": ""},
|
||||
"fsiExtraParameters": [],
|
||||
"generateBinlog": False,
|
||||
"interfaceStubGeneration": True,
|
||||
"interfaceStubGenerationObjectIdentifier": "this",
|
||||
"interfaceStubGenerationMethodBody": "failwith \"Not Implemented\"",
|
||||
"interfaceStubGenerationMethodBody": 'failwith "Not Implemented"',
|
||||
"keywordsAutocomplete": True,
|
||||
"linter": True,
|
||||
"pipelineHints": {"enabled": True},
|
||||
"recordStubGeneration": True,
|
||||
"recordStubGenerationBody": "failwith \"Not Implemented\"",
|
||||
"recordStubGenerationBody": 'failwith "Not Implemented"',
|
||||
"resolveNamespaces": True,
|
||||
"saveOnlyOpenFiles": False,
|
||||
"showProjectExplorerIn": ["ionide", "solution"],
|
||||
@@ -271,12 +265,12 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
"suggestGitignore": True,
|
||||
"suggestSdkScripts": True,
|
||||
"unionCaseStubGeneration": True,
|
||||
"unionCaseStubGenerationBody": "failwith \"Not Implemented\"",
|
||||
"unionCaseStubGenerationBody": 'failwith "Not Implemented"',
|
||||
"unusedDeclarationsAnalyzer": True,
|
||||
"unusedOpensAnalyzer": True,
|
||||
"verboseLogging": False,
|
||||
"workspaceModePeekDeepLevel": 2,
|
||||
"workspacePath": self.repository_root_path
|
||||
"workspacePath": self.repository_root_path,
|
||||
},
|
||||
"trace": "off",
|
||||
}
|
||||
@@ -292,46 +286,48 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
# Try to get the installation path
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run([dotnet_exe, "--info"], capture_output=True, text=True, check=True)
|
||||
lines = result.stdout.split('\n')
|
||||
lines = result.stdout.split("\n")
|
||||
for line in lines:
|
||||
if 'Base Path:' in line or 'Base path:' in line:
|
||||
base_path = line.split(':', 1)[1].strip()
|
||||
if "Base Path:" in line or "Base path:" in line:
|
||||
base_path = line.split(":", 1)[1].strip()
|
||||
# Get the parent directory (remove 'sdk/version' part)
|
||||
return str(Path(base_path).parent.parent)
|
||||
except (subprocess.CalledProcessError, Exception):
|
||||
pass
|
||||
|
||||
|
||||
# Fallback: use the directory containing dotnet executable
|
||||
if dotnet_exe:
|
||||
return str(Path(dotnet_exe).parent)
|
||||
|
||||
|
||||
return ""
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Start the F# Language Server with custom handlers.
|
||||
"""
|
||||
|
||||
def handle_window_log_message(params):
|
||||
"""Handle window/logMessage from the LSP server."""
|
||||
message = params.get("message", "")
|
||||
message_type = params.get("type", 1)
|
||||
|
||||
|
||||
# Map LSP log levels to Python logging levels
|
||||
level_map = {1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG}
|
||||
level = level_map.get(message_type, logging.INFO)
|
||||
|
||||
|
||||
self.logger.log(f"FsAutoComplete: {message}", level)
|
||||
|
||||
def handle_window_show_message(params):
|
||||
"""Handle window/showMessage from the LSP server."""
|
||||
message = params.get("message", "")
|
||||
message_type = params.get("type", 1)
|
||||
|
||||
|
||||
# Map LSP message types to Python logging levels
|
||||
level_map = {1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG}
|
||||
level = level_map.get(message_type, logging.INFO)
|
||||
|
||||
|
||||
self.logger.log(f"FsAutoComplete Message: {message}", level)
|
||||
|
||||
def handle_workspace_configuration(params):
|
||||
@@ -347,7 +343,7 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
|
||||
def handle_client_unregister_capability(params):
|
||||
"""Handle client/unregisterCapability requests from the LSP server."""
|
||||
# For now, just acknowledge the unregistration
|
||||
# For now, just acknowledge the unregistration
|
||||
return None
|
||||
|
||||
def handle_work_done_progress_create(params):
|
||||
@@ -391,4 +387,4 @@ class FSharpLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
F# projects can be large and may need more time for cross-file analysis.
|
||||
"""
|
||||
return 15.0 # 15 seconds should be sufficient for most F# projects
|
||||
return 15.0 # 15 seconds should be sufficient for most F# projects
|
||||
@@ -19,11 +19,11 @@ class TestFSharpLanguageServer:
|
||||
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding symbols in the full symbol tree."""
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
|
||||
|
||||
# Check for main program module symbols
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Program"), "Program module not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree"
|
||||
|
||||
|
||||
# Check for Calculator module symbols
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator module not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree"
|
||||
@@ -62,7 +62,7 @@ class TestFSharpLanguageServer:
|
||||
# Look for expected functions
|
||||
symbol_names = [s.get("name") for s in symbols]
|
||||
expected_symbols = ["add", "subtract", "multiply", "divide", "square", "factorial", "CalculatorClass"]
|
||||
|
||||
|
||||
for expected in expected_symbols:
|
||||
assert expected in symbol_names, f"{expected} function not found in Calculator.fs symbols"
|
||||
|
||||
@@ -71,26 +71,24 @@ class TestFSharpLanguageServer:
|
||||
"""Test finding references using symbol selection range."""
|
||||
file_path = os.path.join("Calculator.fs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
|
||||
|
||||
# Find the 'add' function symbol
|
||||
add_symbol = None
|
||||
symbol_list = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
|
||||
|
||||
|
||||
for sym in symbol_list:
|
||||
if sym.get("name") == "add":
|
||||
add_symbol = sym
|
||||
break
|
||||
|
||||
|
||||
assert add_symbol is not None, "Could not find 'add' function symbol in Calculator.fs"
|
||||
|
||||
|
||||
# Try to find references to the add function
|
||||
sel_start = add_symbol["selectionRange"]["start"]
|
||||
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1)
|
||||
|
||||
|
||||
# The add function should be referenced in Program.fs
|
||||
assert any(
|
||||
"Program.fs" in ref.get("relativePath", "") for ref in refs
|
||||
), "Program.fs should reference add function"
|
||||
assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference add function"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
|
||||
def test_nested_module_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
@@ -108,7 +106,7 @@ class TestFSharpLanguageServer:
|
||||
# Check for expected types and modules
|
||||
symbol_names = [s.get("name") for s in symbols]
|
||||
expected_symbols = ["Person", "PersonModule", "Address", "Employee"]
|
||||
|
||||
|
||||
for expected in expected_symbols:
|
||||
assert expected in symbol_names, f"{expected} not found in Person.fs symbols"
|
||||
|
||||
@@ -135,20 +133,18 @@ class TestFSharpLanguageServer:
|
||||
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1)
|
||||
|
||||
# The subtract function should be referenced in Program.fs
|
||||
assert any(
|
||||
"Program.fs" in ref.get("relativePath", "") for ref in refs
|
||||
), "Program.fs should reference subtract function"
|
||||
assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference subtract function"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
|
||||
def test_go_to_definition(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test go-to-definition functionality."""
|
||||
# Test going to definition of 'add' function from Program.fs
|
||||
program_file = os.path.join("Program.fs")
|
||||
|
||||
|
||||
# Try to find definition of 'add' function used in Program.fs
|
||||
# This would typically be at the line where 'add 5 3' is called
|
||||
definitions = language_server.request_definition(program_file, 10, 20) # Approximate position
|
||||
|
||||
|
||||
# We should get at least some definitions
|
||||
assert len(definitions) >= 0, "Should get definitions (even if empty for complex cases)"
|
||||
|
||||
@@ -156,10 +152,10 @@ class TestFSharpLanguageServer:
|
||||
def test_hover_information(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test hover information functionality."""
|
||||
file_path = os.path.join("Calculator.fs")
|
||||
|
||||
|
||||
# Try to get hover information for a function
|
||||
hover_info = language_server.request_hover(file_path, 5, 10) # Approximate position of a function
|
||||
|
||||
|
||||
# Hover info might be None or contain information
|
||||
# This is acceptable as it depends on the LSP server's capabilities and timing
|
||||
assert hover_info is None or isinstance(hover_info, dict), "Hover info should be None or dict"
|
||||
@@ -168,18 +164,18 @@ class TestFSharpLanguageServer:
|
||||
def test_completion(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test code completion functionality."""
|
||||
file_path = os.path.join("Program.fs")
|
||||
|
||||
|
||||
# F# completion can be slow, so we'll test this with a timeout approach
|
||||
# In real usage, completions work but may take time for large projects
|
||||
try:
|
||||
import signal
|
||||
|
||||
|
||||
def timeout_handler(signum, frame):
|
||||
raise TimeoutError("Completion request timed out")
|
||||
|
||||
|
||||
signal.signal(signal.SIGALRM, timeout_handler)
|
||||
signal.alarm(5) # 5 second timeout
|
||||
|
||||
|
||||
try:
|
||||
completions = language_server.request_completions(file_path, 15, 10)
|
||||
signal.alarm(0) # Cancel alarm
|
||||
@@ -197,16 +193,16 @@ class TestFSharpLanguageServer:
|
||||
def test_diagnostics(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test getting diagnostics (errors, warnings) from F# files."""
|
||||
file_path = os.path.join("Program.fs")
|
||||
|
||||
|
||||
# FsAutoComplete uses publishDiagnostics notifications instead of textDocument/diagnostic requests
|
||||
# So we'll test that the language server can handle files without crashing
|
||||
# In real usage, diagnostics would come through the publishDiagnostics notification handler
|
||||
|
||||
|
||||
# Test that we can at least work with the file (open/close cycle)
|
||||
with language_server.open_file(file_path) as _:
|
||||
# If we can open and close the file without errors, basic diagnostics support is working
|
||||
pass
|
||||
|
||||
|
||||
# This is a successful test - FsAutoComplete is working with F# files
|
||||
assert True, "F# language server can handle files successfully"
|
||||
|
||||
@@ -214,21 +210,19 @@ class TestFSharpLanguageServer:
|
||||
@pytest.mark.fsharp
|
||||
class TestFSharpLanguageServerSetup:
|
||||
"""Test F# language server setup and configuration."""
|
||||
|
||||
|
||||
def test_runtime_dependency_setup_without_dotnet(self) -> None:
|
||||
"""Test that setup fails gracefully when .NET is not installed."""
|
||||
with patch("shutil.which", return_value=None):
|
||||
with pytest.raises(RuntimeError, match=".NET SDK is not installed"):
|
||||
FSharpLanguageServer._setup_runtime_dependencies(
|
||||
Mock(), Mock(), Mock()
|
||||
)
|
||||
FSharpLanguageServer._setup_runtime_dependencies(Mock(), Mock(), Mock())
|
||||
|
||||
def test_runtime_dependency_setup_with_dotnet(self) -> None:
|
||||
"""Test that setup works when .NET is available."""
|
||||
mock_logger = Mock()
|
||||
mock_config = Mock()
|
||||
mock_settings = Mock()
|
||||
|
||||
|
||||
# Mock the ls_resources_dir to return a temp directory
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with patch("shutil.which", return_value="/usr/bin/dotnet"):
|
||||
@@ -237,16 +231,14 @@ class TestFSharpLanguageServerSetup:
|
||||
# Mock successful dotnet version check
|
||||
mock_run.return_value.stdout = "8.0.100"
|
||||
mock_run.return_value.returncode = 0
|
||||
|
||||
|
||||
# Create a fake fsautocomplete executable
|
||||
fsharp_dir = os.path.join(temp_dir, "fsharp-lsp")
|
||||
os.makedirs(fsharp_dir, exist_ok=True)
|
||||
fsautocomplete_path = os.path.join(fsharp_dir, "fsautocomplete")
|
||||
Path(fsautocomplete_path).touch()
|
||||
|
||||
result = FSharpLanguageServer._setup_runtime_dependencies(
|
||||
mock_logger, mock_config, mock_settings
|
||||
)
|
||||
|
||||
|
||||
result = FSharpLanguageServer._setup_runtime_dependencies(mock_logger, mock_config, mock_settings)
|
||||
|
||||
assert fsautocomplete_path in result
|
||||
assert "--lsp" in result
|
||||
assert "--lsp" in result
|
||||
Reference in new issue
Block a user