Formatting, minor ruff fixes

This commit is contained in:
Michael Panchenko committed 2025-06-28 23:42:44 +02:00
1 parent 6e2ed3969d
commit 3d4dba77bd
3 files changed
+131 -140

No files matched your search

@@ -6,7 +6,6 @@ import logging
import os import os
import pathlib import pathlib
import platform import platform
import shlex
import shutil import shutil
import stat import stat
import subprocess import subprocess
@@ -56,17 +55,17 @@ def find_solution_or_project_file(root_dir) -> str | None:
""" """
sln_file = None sln_file = None
csproj_file = None csproj_file = None
for filename in breadth_first_file_scan(root_dir): for filename in breadth_first_file_scan(root_dir):
if filename.endswith(".sln") and sln_file is None: if filename.endswith(".sln") and sln_file is None:
sln_file = filename sln_file = filename
elif filename.endswith(".csproj") and csproj_file is None: elif filename.endswith(".csproj") and csproj_file is None:
csproj_file = filename csproj_file = filename
# If we found a .sln file, return it immediately # If we found a .sln file, return it immediately
if sln_file: if sln_file:
return sln_file return sln_file
# If no .sln file was found, return the first .csproj file # If no .sln file was found, return the first .csproj file
return csproj_file return csproj_file
@@ -83,39 +82,39 @@ class CSharpLanguageServer(SolidLanguageServer):
Use LanguageServer.create() instead. Use LanguageServer.create() instead.
""" """
dotnet_path, language_server_path, cache_dir = self.setup_runtime_dependencies(logger, config) dotnet_path, language_server_path, cache_dir = self.setup_runtime_dependencies(logger, config)
# Find solution or project file # Find solution or project file
solution_or_project = find_solution_or_project_file(repository_root_path) solution_or_project = find_solution_or_project_file(repository_root_path)
# Build command - Microsoft.CodeAnalysis.LanguageServer requires specific parameters # Build command - Microsoft.CodeAnalysis.LanguageServer requires specific parameters
cmd_parts = [dotnet_path, language_server_path] cmd_parts = [dotnet_path, language_server_path]
# Required parameters # Required parameters
cmd_parts.extend(["--stdio"]) # Use stdio for communication cmd_parts.extend(["--stdio"]) # Use stdio for communication
# Set log level based on logger settings # Set log level based on logger settings
if logger.logger.level <= logging.DEBUG: if logger.logger.level <= logging.DEBUG:
cmd_parts.extend(["--logLevel", "Information"]) cmd_parts.extend(["--logLevel", "Information"])
else: else:
cmd_parts.extend(["--logLevel", "Warning"]) cmd_parts.extend(["--logLevel", "Warning"])
# Create log directory # Create log directory
log_dir = cache_dir / "logs" log_dir = cache_dir / "logs"
log_dir.mkdir(parents=True, exist_ok=True) log_dir.mkdir(parents=True, exist_ok=True)
cmd_parts.extend(["--extensionLogDirectory", str(log_dir)]) cmd_parts.extend(["--extensionLogDirectory", str(log_dir)])
# The language server will discover the solution/project from the workspace root # The language server will discover the solution/project from the workspace root
if solution_or_project: if solution_or_project:
logger.log(f"Found solution/project file: {solution_or_project}", logging.INFO) logger.log(f"Found solution/project file: {solution_or_project}", logging.INFO)
else: else:
logger.log("No .sln or .csproj file found, language server will attempt auto-discovery", logging.WARNING) logger.log("No .sln or .csproj file found, language server will attempt auto-discovery", logging.WARNING)
# Join command parts with spaces # Join command parts with spaces
# ProcessLaunchInfo expects a simple string command, not shell-quoted # ProcessLaunchInfo expects a simple string command, not shell-quoted
cmd = " ".join(cmd_parts) cmd = " ".join(cmd_parts)
logger.log(f"Language server command: {cmd}", logging.DEBUG) logger.log(f"Language server command: {cmd}", logging.DEBUG)
super().__init__( super().__init__(
config, config,
logger, logger,
@@ -123,7 +122,7 @@ class CSharpLanguageServer(SolidLanguageServer):
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
"csharp", "csharp",
) )
self.initialization_complete = threading.Event() self.initialization_complete = threading.Event()
@override @override
@@ -136,15 +135,15 @@ class CSharpLanguageServer(SolidLanguageServer):
Returns a tuple of (dotnet_path, language_server_dll_path, cache_dir). Returns a tuple of (dotnet_path, language_server_dll_path, cache_dir).
""" """
import json import json
# Load runtime dependencies configuration # Load runtime dependencies configuration
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), encoding="utf-8") as f: with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), encoding="utf-8") as f:
runtime_deps = json.load(f) runtime_deps = json.load(f)
# Determine the runtime ID based on the platform # Determine the runtime ID based on the platform
system = platform.system().lower() system = platform.system().lower()
machine = platform.machine().lower() machine = platform.machine().lower()
# Map platform info to runtime ID # Map platform info to runtime ID
if system == "windows": if system == "windows":
runtime_id = "win-x64" if machine in ["amd64", "x86_64"] else "win-arm64" runtime_id = "win-x64" if machine in ["amd64", "x86_64"] else "win-arm64"
@@ -154,80 +153,78 @@ class CSharpLanguageServer(SolidLanguageServer):
runtime_id = "linux-x64" if machine in ["x86_64", "amd64"] else "linux-arm64" runtime_id = "linux-x64" if machine in ["x86_64", "amd64"] else "linux-arm64"
else: else:
raise LanguageServerException(f"Unsupported platform: {system} {machine}") raise LanguageServerException(f"Unsupported platform: {system} {machine}")
# Set up cache directory # Set up cache directory
cache_dir = Path.home() / ".cache" / "serena" / "language-servers" / "csharp" cache_dir = Path.home() / ".cache" / "serena" / "language-servers" / "csharp"
cache_dir.mkdir(parents=True, exist_ok=True) cache_dir.mkdir(parents=True, exist_ok=True)
# Find the appropriate language server dependency # Find the appropriate language server dependency
lang_server_dep = None lang_server_dep = None
dotnet_runtime_dep = None dotnet_runtime_dep = None
for dep in runtime_deps["runtimeDependencies"]: for dep in runtime_deps["runtimeDependencies"]:
if dep["id"] == "CSharpLanguageServer" and dep["platformId"] == runtime_id: if dep["id"] == "CSharpLanguageServer" and dep["platformId"] == runtime_id:
lang_server_dep = dep lang_server_dep = dep
elif dep["id"] == "DotNetRuntime" and dep["platformId"] == runtime_id: elif dep["id"] == "DotNetRuntime" and dep["platformId"] == runtime_id:
dotnet_runtime_dep = dep dotnet_runtime_dep = dep
if not lang_server_dep: if not lang_server_dep:
raise LanguageServerException(f"No C# language server dependency found for platform {runtime_id}") raise LanguageServerException(f"No C# language server dependency found for platform {runtime_id}")
if not dotnet_runtime_dep: if not dotnet_runtime_dep:
raise LanguageServerException(f"No .NET runtime dependency found for platform {runtime_id}") raise LanguageServerException(f"No .NET runtime dependency found for platform {runtime_id}")
# First, ensure we have .NET 9 runtime # First, ensure we have .NET 9 runtime
dotnet_path = self._ensure_dotnet_runtime_from_config(logger, cache_dir, dotnet_runtime_dep) dotnet_path = self._ensure_dotnet_runtime_from_config(logger, cache_dir, dotnet_runtime_dep)
# Then set up the language server # Then set up the language server
package_name = lang_server_dep["packageName"] package_name = lang_server_dep["packageName"]
package_version = lang_server_dep["packageVersion"] package_version = lang_server_dep["packageVersion"]
server_dir = cache_dir / f"{package_name}.{package_version}" server_dir = cache_dir / f"{package_name}.{package_version}"
server_dll = server_dir / lang_server_dep["binaryName"] server_dll = server_dir / lang_server_dep["binaryName"]
if server_dll.exists(): if server_dll.exists():
logger.log(f"Using cached Microsoft.CodeAnalysis.LanguageServer from {server_dll}", logging.INFO) logger.log(f"Using cached Microsoft.CodeAnalysis.LanguageServer from {server_dll}", logging.INFO)
return dotnet_path, str(server_dll), cache_dir return dotnet_path, str(server_dll), cache_dir
# Download the language server package # Download the language server package
logger.log(f"Downloading {package_name} version {package_version}...", logging.INFO) logger.log(f"Downloading {package_name} version {package_version}...", logging.INFO)
# Check if nuget or dotnet is available # Check if nuget or dotnet is available
nuget_cmd = shutil.which("nuget") nuget_cmd = shutil.which("nuget")
dotnet_cmd = shutil.which("dotnet") dotnet_cmd = shutil.which("dotnet")
if not nuget_cmd and not dotnet_cmd: if not nuget_cmd and not dotnet_cmd:
raise LanguageServerException( raise LanguageServerException(
"Neither nuget nor dotnet CLI is available. " "Neither nuget nor dotnet CLI is available. " "Please install .NET SDK from https://dotnet.microsoft.com/download"
"Please install .NET SDK from https://dotnet.microsoft.com/download"
) )
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir) temp_path = Path(temp_dir)
# Try to download directly from the NuGet API first # Try to download directly from the NuGet API first
direct_download_url = f"https://api.nuget.org/v3-flatcontainer/{package_name.lower()}/{package_version}/{package_name.lower()}.{package_version}.nupkg" direct_download_url = f"https://api.nuget.org/v3-flatcontainer/{package_name.lower()}/{package_version}/{package_name.lower()}.{package_version}.nupkg"
package_path = None package_path = None
try: try:
nupkg_path = temp_path / f"{package_name}.{package_version}.nupkg" nupkg_path = temp_path / f"{package_name}.{package_version}.nupkg"
logger.log(f"Attempting direct download from {direct_download_url}", logging.INFO) logger.log(f"Attempting direct download from {direct_download_url}", logging.INFO)
urllib.request.urlretrieve(direct_download_url, nupkg_path) urllib.request.urlretrieve(direct_download_url, nupkg_path)
# Extract the nupkg (it's a zip file) # Extract the nupkg (it's a zip file)
package_path = temp_path / f"{package_name}.{package_version}" package_path = temp_path / f"{package_name}.{package_version}"
with zipfile.ZipFile(nupkg_path, 'r') as zip_ref: with zipfile.ZipFile(nupkg_path, "r") as zip_ref:
zip_ref.extractall(package_path) zip_ref.extractall(package_path)
logger.log("Successfully downloaded and extracted package", logging.INFO) logger.log("Successfully downloaded and extracted package", logging.INFO)
except Exception as e: except Exception as e:
logger.log(f"Direct download failed: {e}, falling back to package manager", logging.WARNING) logger.log(f"Direct download failed: {e}, falling back to package manager", logging.WARNING)
package_path = None package_path = None
if package_path is None and dotnet_cmd: if package_path is None and dotnet_cmd:
# Use dotnet restore to download the package # Use dotnet restore to download the package
nuget_sources = " ".join([f"--source {src}" for src in runtime_deps["nugetSources"]])
project_content = f"""<Project Sdk="Microsoft.NET.Sdk"> project_content = f"""<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
@@ -241,84 +238,90 @@ class CSharpLanguageServer(SolidLanguageServer):
</RestoreAdditionalProjectSources> </RestoreAdditionalProjectSources>
</PropertyGroup> </PropertyGroup>
</Project>""" </Project>"""
project_file = temp_path / "temp.csproj" project_file = temp_path / "temp.csproj"
project_file.write_text(project_content) project_file.write_text(project_content)
try: try:
# Use dotnet restore with no dependencies # Use dotnet restore with no dependencies
subprocess.run( subprocess.run(
[dotnet_cmd, "restore", str(project_file), "--packages", str(temp_path), [
"--no-dependencies", "--ignore-failed-sources"], dotnet_cmd,
"restore",
str(project_file),
"--packages",
str(temp_path),
"--no-dependencies",
"--ignore-failed-sources",
],
check=True, check=True,
capture_output=True, capture_output=True,
text=True text=True,
) )
package_path = temp_path / package_name.lower() / package_version package_path = temp_path / package_name.lower() / package_version
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
logger.log(f"Dotnet restore stdout: {e.stdout}", logging.ERROR) logger.log(f"Dotnet restore stdout: {e.stdout}", logging.ERROR)
logger.log(f"Dotnet restore stderr: {e.stderr}", logging.ERROR) logger.log(f"Dotnet restore stderr: {e.stderr}", logging.ERROR)
raise LanguageServerException(f"Failed to download package: stdout={e.stdout}, stderr={e.stderr}") raise LanguageServerException(f"Failed to download package: stdout={e.stdout}, stderr={e.stderr}")
elif package_path is None and nuget_cmd: elif package_path is None and nuget_cmd:
# Use nuget to download the package # Use nuget to download the package
try: try:
subprocess.run( subprocess.run(
[ [
nuget_cmd, "install", package_name, nuget_cmd,
"-Version", package_version, "install",
"-OutputDirectory", str(temp_path), package_name,
"-NonInteractive" "-Version",
package_version,
"-OutputDirectory",
str(temp_path),
"-NonInteractive",
], ],
check=True, check=True,
capture_output=True, capture_output=True,
text=True text=True,
) )
# Find the downloaded package # Find the downloaded package
package_path = temp_path / f"{package_name}.{package_version}" package_path = temp_path / f"{package_name}.{package_version}"
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
raise LanguageServerException(f"Failed to download package: {e.stderr}") raise LanguageServerException(f"Failed to download package: {e.stderr}")
if package_path is None or not package_path.exists(): if package_path is None or not package_path.exists():
raise LanguageServerException("Failed to download Microsoft.CodeAnalysis.LanguageServer package") raise LanguageServerException("Failed to download Microsoft.CodeAnalysis.LanguageServer package")
# Extract the language server files # Extract the language server files
extract_path = lang_server_dep.get("extractPath", "lib/net9.0") extract_path = lang_server_dep.get("extractPath", "lib/net9.0")
source_dir = package_path / extract_path source_dir = package_path / extract_path
if not source_dir.exists(): if not source_dir.exists():
# Try alternative locations # Try alternative locations
for possible_dir in [ for possible_dir in [
package_path / "tools" / "net9.0" / "any", package_path / "tools" / "net9.0" / "any",
package_path / "lib" / "net9.0", package_path / "lib" / "net9.0",
package_path / "contentFiles" / "any" / "net9.0" package_path / "contentFiles" / "any" / "net9.0",
]: ]:
if possible_dir.exists(): if possible_dir.exists():
source_dir = possible_dir source_dir = possible_dir
break break
else: else:
raise LanguageServerException( raise LanguageServerException(f"Could not find language server files in package. " f"Searched in {package_path}")
f"Could not find language server files in package. "
f"Searched in {package_path}"
)
# Copy files to cache directory # Copy files to cache directory
server_dir.mkdir(parents=True, exist_ok=True) server_dir.mkdir(parents=True, exist_ok=True)
shutil.copytree(source_dir, server_dir, dirs_exist_ok=True) shutil.copytree(source_dir, server_dir, dirs_exist_ok=True)
if not server_dll.exists(): if not server_dll.exists():
raise LanguageServerException( raise LanguageServerException("Microsoft.CodeAnalysis.LanguageServer.dll not found after extraction")
"Microsoft.CodeAnalysis.LanguageServer.dll not found after extraction"
)
# Make the DLL executable on Unix-like systems # Make the DLL executable on Unix-like systems
if system != "windows": if system != "windows":
server_dll.chmod(server_dll.stat().st_mode | stat.S_IEXEC) server_dll.chmod(server_dll.stat().st_mode | stat.S_IEXEC)
logger.log(f"Successfully installed Microsoft.CodeAnalysis.LanguageServer to {server_dll}", logging.INFO) logger.log(f"Successfully installed Microsoft.CodeAnalysis.LanguageServer to {server_dll}", logging.INFO)
return dotnet_path, str(server_dll), cache_dir return dotnet_path, str(server_dll), cache_dir
@@ -332,22 +335,17 @@ class CSharpLanguageServer(SolidLanguageServer):
if system_dotnet: if system_dotnet:
# Check if it's .NET 9 # Check if it's .NET 9
try: try:
result = subprocess.run( result = subprocess.run([system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
[system_dotnet, "--list-runtimes"],
capture_output=True,
text=True,
check=True
)
if "Microsoft.NETCore.App 9." in result.stdout: if "Microsoft.NETCore.App 9." in result.stdout:
logger.log("Found system .NET 9 runtime", logging.INFO) logger.log("Found system .NET 9 runtime", logging.INFO)
return system_dotnet return system_dotnet
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
pass pass
# Download .NET 9 runtime using config # Download .NET 9 runtime using config
dotnet_dir = cache_dir / "dotnet-runtime-9.0" dotnet_dir = cache_dir / "dotnet-runtime-9.0"
dotnet_exe = dotnet_dir / runtime_dep["binaryName"] dotnet_exe = dotnet_dir / runtime_dep["binaryName"]
if dotnet_exe.exists(): if dotnet_exe.exists():
# Verify it still works # Verify it still works
try: try:
@@ -357,75 +355,73 @@ class CSharpLanguageServer(SolidLanguageServer):
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
logger.log("Cached .NET runtime is corrupted, re-downloading", logging.WARNING) logger.log("Cached .NET runtime is corrupted, re-downloading", logging.WARNING)
shutil.rmtree(dotnet_dir, ignore_errors=True) shutil.rmtree(dotnet_dir, ignore_errors=True)
# Download .NET runtime # Download .NET runtime
logger.log("Downloading .NET 9 runtime...", logging.INFO) logger.log("Downloading .NET 9 runtime...", logging.INFO)
dotnet_dir.mkdir(parents=True, exist_ok=True) dotnet_dir.mkdir(parents=True, exist_ok=True)
url = runtime_dep["url"] url = runtime_dep["url"]
archive_type = runtime_dep["archiveType"] archive_type = runtime_dep["archiveType"]
# Download the runtime # Download the runtime
download_path = dotnet_dir / f"dotnet-runtime.{archive_type}" download_path = dotnet_dir / f"dotnet-runtime.{archive_type}"
try: try:
logger.log(f"Downloading from {url}", logging.DEBUG) logger.log(f"Downloading from {url}", logging.DEBUG)
urllib.request.urlretrieve(url, download_path) urllib.request.urlretrieve(url, download_path)
# Extract the archive # Extract the archive
if archive_type == "zip": if archive_type == "zip":
with zipfile.ZipFile(download_path, 'r') as zip_ref: with zipfile.ZipFile(download_path, "r") as zip_ref:
zip_ref.extractall(dotnet_dir) zip_ref.extractall(dotnet_dir)
else: else:
# tar.gz # tar.gz
with tarfile.open(download_path, 'r:gz') as tar_ref: with tarfile.open(download_path, "r:gz") as tar_ref:
tar_ref.extractall(dotnet_dir) tar_ref.extractall(dotnet_dir)
# Remove the archive # Remove the archive
download_path.unlink() download_path.unlink()
# Make dotnet executable on Unix # Make dotnet executable on Unix
if platform.system().lower() != "windows": if platform.system().lower() != "windows":
dotnet_exe.chmod(dotnet_exe.stat().st_mode | stat.S_IEXEC) dotnet_exe.chmod(dotnet_exe.stat().st_mode | stat.S_IEXEC)
logger.log(f"Successfully installed .NET 9 runtime to {dotnet_exe}", logging.INFO) logger.log(f"Successfully installed .NET 9 runtime to {dotnet_exe}", logging.INFO)
return str(dotnet_exe) return str(dotnet_exe)
except Exception as e: except Exception as e:
raise LanguageServerException(f"Failed to download .NET 9 runtime from {url}: {e}") from e raise LanguageServerException(f"Failed to download .NET 9 runtime from {url}: {e}") from e
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
""" """
Returns the initialize params for the Microsoft.CodeAnalysis.LanguageServer. Returns the initialize params for the Microsoft.CodeAnalysis.LanguageServer.
""" """
import json import json
# Load initialization parameters from JSON file # Load initialization parameters from JSON file
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f: with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f:
initialize_params_template = json.load(f) initialize_params_template = json.load(f)
# Convert the template string to actual values # Convert the template string to actual values
initialize_params_str = json.dumps(initialize_params_template) initialize_params_str = json.dumps(initialize_params_template)
# Replace template variables # Replace template variables
root_uri = pathlib.Path(repository_absolute_path).as_uri() root_uri = pathlib.Path(repository_absolute_path).as_uri()
root_name = os.path.basename(repository_absolute_path) root_name = os.path.basename(repository_absolute_path)
initialize_params_str = initialize_params_str.replace("$rootPath", repository_absolute_path) initialize_params_str = initialize_params_str.replace("$rootPath", repository_absolute_path)
initialize_params_str = initialize_params_str.replace("$rootUri", root_uri) initialize_params_str = initialize_params_str.replace("$rootUri", root_uri)
initialize_params_str = initialize_params_str.replace("$rootName", root_name) initialize_params_str = initialize_params_str.replace("$rootName", root_name)
initialize_params_str = initialize_params_str.replace('"os.getpid()"', str(os.getpid())) initialize_params_str = initialize_params_str.replace('"os.getpid()"', str(os.getpid()))
# Parse back to dictionary # Parse back to dictionary
initialize_params = json.loads(initialize_params_str) initialize_params = json.loads(initialize_params_str)
return initialize_params return initialize_params
def _start_server(self): def _start_server(self):
""" """
Starts the Microsoft.CodeAnalysis.LanguageServer. Starts the Microsoft.CodeAnalysis.LanguageServer.
Usage: Usage:
``` ```
async with lsp.start_server(): async with lsp.start_server():
@@ -436,7 +432,7 @@ class CSharpLanguageServer(SolidLanguageServer):
# LanguageServer has been shutdown cleanly # LanguageServer has been shutdown cleanly
``` ```
""" """
def do_nothing(params): def do_nothing(params):
return return
@@ -444,17 +440,12 @@ class CSharpLanguageServer(SolidLanguageServer):
"""Log messages from the language server.""" """Log messages from the language server."""
message_text = msg.get("message", "") message_text = msg.get("message", "")
level = msg.get("type", 4) # Default to Log level level = msg.get("type", 4) # Default to Log level
# Map LSP message types to Python logging levels # Map LSP message types to Python logging levels
level_map = { level_map = {1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG} # Error # Warning # Info # Log
1: logging.ERROR, # Error
2: logging.WARNING, # Warning
3: logging.INFO, # Info
4: logging.DEBUG # Log
}
self.logger.log(f"LSP: {message_text}", level_map.get(level, logging.DEBUG)) self.logger.log(f"LSP: {message_text}", level_map.get(level, logging.DEBUG))
# Also print important messages to console # Also print important messages to console
if level <= 3: # Error, Warning, or Info if level <= 3: # Error, Warning, or Info
print(f"C# Language Server: {message_text}") print(f"C# Language Server: {message_text}")
@@ -463,36 +454,36 @@ class CSharpLanguageServer(SolidLanguageServer):
"""Handle progress notifications from the language server.""" """Handle progress notifications from the language server."""
token = params.get("token", "") token = params.get("token", "")
value = params.get("value", {}) value = params.get("value", {})
# Log raw progress for debugging # Log raw progress for debugging
self.logger.log(f"Progress notification received: {params}", logging.DEBUG) self.logger.log(f"Progress notification received: {params}", logging.DEBUG)
# Handle different progress notification types # Handle different progress notification types
kind = value.get("kind") kind = value.get("kind")
if kind == "begin": if kind == "begin":
title = value.get("title", "Operation in progress") title = value.get("title", "Operation in progress")
message = value.get("message", "") message = value.get("message", "")
percentage = value.get("percentage") percentage = value.get("percentage")
if percentage is not None: if percentage is not None:
self.logger.log(f"Progress [{token}]: {title} - {message} ({percentage}%)", logging.INFO) self.logger.log(f"Progress [{token}]: {title} - {message} ({percentage}%)", logging.INFO)
print(f"C# Language Server: {title} - {message} ({percentage}%)") print(f"C# Language Server: {title} - {message} ({percentage}%)")
else: else:
self.logger.log(f"Progress [{token}]: {title} - {message}", logging.INFO) self.logger.log(f"Progress [{token}]: {title} - {message}", logging.INFO)
print(f"C# Language Server: {title} - {message}") print(f"C# Language Server: {title} - {message}")
elif kind == "report": elif kind == "report":
message = value.get("message", "") message = value.get("message", "")
percentage = value.get("percentage") percentage = value.get("percentage")
if percentage is not None: if percentage is not None:
self.logger.log(f"Progress [{token}]: {message} ({percentage}%)", logging.INFO) self.logger.log(f"Progress [{token}]: {message} ({percentage}%)", logging.INFO)
if percentage % 10 == 0: # Print every 10% if percentage % 10 == 0: # Print every 10%
print(f"C# Language Server: {message} ({percentage}%)") print(f"C# Language Server: {message} ({percentage}%)")
elif message: elif message:
self.logger.log(f"Progress [{token}]: {message}", logging.INFO) self.logger.log(f"Progress [{token}]: {message}", logging.INFO)
elif kind == "end": elif kind == "end":
message = value.get("message", "Operation completed") message = value.get("message", "Operation completed")
self.logger.log(f"Progress [{token}]: {message}", logging.INFO) self.logger.log(f"Progress [{token}]: {message}", logging.INFO)
@@ -502,12 +493,12 @@ class CSharpLanguageServer(SolidLanguageServer):
"""Handle workspace/configuration requests from the server.""" """Handle workspace/configuration requests from the server."""
items = params.get("items", []) items = params.get("items", [])
result = [] result = []
for item in items: for item in items:
section = item.get("section", "") section = item.get("section", "")
# Provide default values based on the configuration section # Provide default values based on the configuration section
if section.startswith("dotnet") or section.startswith("csharp"): if section.startswith(("dotnet", "csharp")):
# Default configuration for C# settings # Default configuration for C# settings
if "enable" in section or "show" in section or "suppress" in section or "navigate" in section: if "enable" in section or "show" in section or "suppress" in section or "navigate" in section:
# Boolean settings # Boolean settings
@@ -524,7 +515,7 @@ class CSharpLanguageServer(SolidLanguageServer):
# ImplementTypeInsertionBehavior enum # ImplementTypeInsertionBehavior enum
result.append("with_other_members_of_the_same_kind") result.append("with_other_members_of_the_same_kind")
elif section == "dotnet_property_generation_behavior": elif section == "dotnet_property_generation_behavior":
# ImplementTypePropertyGenerationBehavior enum # ImplementTypePropertyGenerationBehavior enum
result.append("prefer_throwing_properties") result.append("prefer_throwing_properties")
elif "location" in section or "behavior" in section: elif "location" in section or "behavior" in section:
# Other enum settings - return null to avoid parsing errors # Other enum settings - return null to avoid parsing errors
@@ -541,18 +532,18 @@ class CSharpLanguageServer(SolidLanguageServer):
else: else:
# Unknown configuration - return null # Unknown configuration - return null
result.append(None) result.append(None)
return result return result
def handle_work_done_progress_create(params): def handle_work_done_progress_create(params):
"""Handle work done progress create requests.""" """Handle work done progress create requests."""
# Just acknowledge the request # Just acknowledge the request
return None return
def handle_register_capability(params): def handle_register_capability(params):
"""Handle client/registerCapability requests.""" """Handle client/registerCapability requests."""
# Just acknowledge the request - we don't need to track these for now # Just acknowledge the request - we don't need to track these for now
return None return
# Set up notification handlers # Set up notification handlers
self.server.on_notification("window/logMessage", window_log_message) self.server.on_notification("window/logMessage", window_log_message)
@@ -561,18 +552,18 @@ class CSharpLanguageServer(SolidLanguageServer):
self.server.on_request("workspace/configuration", handle_workspace_configuration) self.server.on_request("workspace/configuration", handle_workspace_configuration)
self.server.on_request("window/workDoneProgress/create", handle_work_done_progress_create) self.server.on_request("window/workDoneProgress/create", handle_work_done_progress_create)
self.server.on_request("client/registerCapability", handle_register_capability) self.server.on_request("client/registerCapability", handle_register_capability)
self.logger.log("Starting Microsoft.CodeAnalysis.LanguageServer process", logging.INFO) self.logger.log("Starting Microsoft.CodeAnalysis.LanguageServer process", logging.INFO)
try: try:
self.server.start() self.server.start()
except Exception as e: except Exception as e:
self.logger.log(f"Failed to start language server process: {e}", logging.ERROR) self.logger.log(f"Failed to start language server process: {e}", logging.ERROR)
raise LanguageServerException(f"Failed to start C# language server: {e}") raise LanguageServerException(f"Failed to start C# language server: {e}")
# Send initialization # Send initialization
initialize_params = self._get_initialize_params(self.repository_root_path) initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log("Sending initialize request to language server", logging.INFO) self.logger.log("Sending initialize request to language server", logging.INFO)
try: try:
init_response = self.server.send.initialize(initialize_params) init_response = self.server.send.initialize(initialize_params)
@@ -584,19 +575,19 @@ class CSharpLanguageServer(SolidLanguageServer):
self.logger.log("2. Microsoft.CodeAnalysis.LanguageServer not found", logging.ERROR) self.logger.log("2. Microsoft.CodeAnalysis.LanguageServer not found", logging.ERROR)
self.logger.log("3. Project too large or complex for default timeout", logging.ERROR) self.logger.log("3. Project too large or complex for default timeout", logging.ERROR)
raise raise
# Verify required capabilities # Verify required capabilities
capabilities = init_response.get("capabilities", {}) capabilities = init_response.get("capabilities", {})
assert "textDocumentSync" in capabilities assert "textDocumentSync" in capabilities
assert "definitionProvider" in capabilities assert "definitionProvider" in capabilities
assert "referencesProvider" in capabilities assert "referencesProvider" in capabilities
assert "documentSymbolProvider" in capabilities assert "documentSymbolProvider" in capabilities
# Complete initialization # Complete initialization
self.server.notify.initialized({}) self.server.notify.initialized({})
self.initialization_complete.set() self.initialization_complete.set()
self.completions_available.set() self.completions_available.set()
self.logger.log("Microsoft.CodeAnalysis.LanguageServer initialized and ready", logging.INFO) self.logger.log("Microsoft.CodeAnalysis.LanguageServer initialized and ready", logging.INFO)
self.logger.log("Waiting for language server to index project files...", logging.INFO) self.logger.log("Waiting for language server to index project files...", logging.INFO)
self.logger.log("This may take a while for large projects", logging.INFO) self.logger.log("This may take a while for large projects", logging.INFO)
+1 -1
View File
@@ -139,7 +139,7 @@ class SolidLanguageServer(ABC):
# But OmniSharp seems to crash and has some issues with newer C# versions # But OmniSharp seems to crash and has some issues with newer C# versions
# from solidlsp.language_servers.omnisharp.omnisharp import OmniSharp # from solidlsp.language_servers.omnisharp.omnisharp import OmniSharp
# ls = OmniSharp(config, logger, repository_root_path) # ls = OmniSharp(config, logger, repository_root_path)
from solidlsp.language_servers.csharp_language_server.csharp_language_server import CSharpLanguageServer from solidlsp.language_servers.csharp_language_server.csharp_language_server import CSharpLanguageServer
ls = CSharpLanguageServer(config, logger, repository_root_path) ls = CSharpLanguageServer(config, logger, repository_root_path)
+11 -11
View File
@@ -36,14 +36,14 @@ class TestCSharpBasic:
"""Test getting document symbols from a C# file.""" """Test getting document symbols from a C# file."""
file_path = os.path.join("Program.cs") file_path = os.path.join("Program.cs")
symbols = csharp_ls.request_document_symbols(file_path) symbols = csharp_ls.request_document_symbols(file_path)
# Check that we have symbols # Check that we have symbols
assert len(symbols) > 0 assert len(symbols) > 0
# Flatten the symbols if they're nested # Flatten the symbols if they're nested
if isinstance(symbols[0], list): if isinstance(symbols[0], list):
symbols = symbols[0] symbols = symbols[0]
# Look for expected classes # Look for expected classes
class_names = [s.get("name") for s in symbols if s.get("kind") == 5] # 5 is class class_names = [s.get("name") for s in symbols if s.get("kind") == 5] # 5 is class
assert "Program" in class_names assert "Program" in class_names
@@ -52,12 +52,12 @@ class TestCSharpBasic:
def test_find_definition(self, csharp_ls: SolidLanguageServer): def test_find_definition(self, csharp_ls: SolidLanguageServer):
"""Test finding definition of a symbol.""" """Test finding definition of a symbol."""
file_path = os.path.join("Program.cs") file_path = os.path.join("Program.cs")
# Open the file first # Open the file first
with csharp_ls.open_file(file_path): with csharp_ls.open_file(file_path):
# Find usage of Calculator class (line 11, column 28) # Find usage of Calculator class (line 11, column 28)
definitions = csharp_ls.request_definition(file_path, 11, 28) definitions = csharp_ls.request_definition(file_path, 11, 28)
# Should find the Calculator class definition # Should find the Calculator class definition
assert len(definitions) > 0 assert len(definitions) > 0
assert any("Calculator" in str(d) for d in definitions) assert any("Calculator" in str(d) for d in definitions)
@@ -65,12 +65,12 @@ class TestCSharpBasic:
def test_find_references(self, csharp_ls: SolidLanguageServer): def test_find_references(self, csharp_ls: SolidLanguageServer):
"""Test finding references to a symbol.""" """Test finding references to a symbol."""
file_path = os.path.join("Program.cs") file_path = os.path.join("Program.cs")
# Open the file first # Open the file first
with csharp_ls.open_file(file_path): with csharp_ls.open_file(file_path):
# Find references to the Add method (line 19) # Find references to the Add method (line 19)
references = csharp_ls.request_references(file_path, 19, 20, include_declaration=True) references = csharp_ls.request_references(file_path, 19, 20, include_declaration=True)
# Should find at least the definition and one usage # Should find at least the definition and one usage
assert len(references) >= 2 assert len(references) >= 2
@@ -78,17 +78,17 @@ class TestCSharpBasic:
"""Test getting symbols from nested namespace.""" """Test getting symbols from nested namespace."""
file_path = os.path.join("Models", "Person.cs") file_path = os.path.join("Models", "Person.cs")
symbols = csharp_ls.request_document_symbols(file_path) symbols = csharp_ls.request_document_symbols(file_path)
# Check that we have symbols # Check that we have symbols
assert len(symbols) > 0 assert len(symbols) > 0
# Flatten the symbols if they're nested # Flatten the symbols if they're nested
if isinstance(symbols[0], list): if isinstance(symbols[0], list):
symbols = symbols[0] symbols = symbols[0]
# Check that we have the Person class # Check that we have the Person class
assert any(s.get("name") == "Person" and s.get("kind") == 5 for s in symbols) assert any(s.get("name") == "Person" and s.get("kind") == 5 for s in symbols)
# Check for properties and methods # Check for properties and methods
symbol_names = [s.get("name") for s in symbols] symbol_names = [s.get("name") for s in symbols]
assert "Name" in symbol_names assert "Name" in symbol_names