Remove LanguageServerLogger

Resolves #745
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2025-11-20 21:33:37 +01:00
1 parent 4702fbe1ea
commit 82d495b2a6
45 files changed
+659 -1120

No files matched your search

@@ -21,39 +21,35 @@ Have a look at `intelephense.py` for a reference implementation of a language se
dependencies, and on `pyright_server.py` that does not need any additional dependencies
because the language server can be installed directly as python package.
```python
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
class NewLanguageServer(SolidLanguageServer):
"""
Language server implementation for NewLanguage.
"""
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
def __init__(self, config: LanguageServerConfig, repository_root_path: str):
# Determine language server command
cmd = self._get_language_server_command()
super().__init__(
config,
logger,
repository_root_path,
super().__init__(config,
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
"new_language", # Language ID for LSP
)
"new_language",,
def _get_language_server_command(self) -> list[str]:
"""Get the command to start the language server."""
# Example: return ["new-language-server", "--stdio"]
pass
@override
def is_ignored_dirname(self, dirname: str) -> bool:
"""Define language-specific directories to ignore."""
return super().is_ignored_dirname(dirname) or dirname in ["build", "dist", "target"]
return super().is_ignored_dirname(dirname) or dirname in ["build",
"dist", "target"]
```
### 1.2 Language Server Discovery and Installation
@@ -62,7 +58,7 @@ For languages requiring automatic installation, implement download logic similar
```python
@classmethod
def _ensure_server_installed(cls, logger: LanguageServerLogger) -> str:
def _ensure_server_installed(cls) -> str:
"""Ensure language server is installed and return path."""
# Check system installation first
system_server = shutil.which("new-language-server")
@@ -70,10 +66,10 @@ def _ensure_server_installed(cls, logger: LanguageServerLogger) -> str:
return system_server
# Download and install if needed
server_path = cls._download_and_install_server(logger)
server_path = cls._download_and_install_server()
return server_path
def _download_and_install_server(cls, logger: LanguageServerLogger) -> str:
def _download_and_install_server(cls) -> str:
"""Download and install the language server."""
# Implementation specific to your language server
pass
@@ -129,12 +125,12 @@ In `src/solidlsp/ls.py`, add your language to the `create` method:
```python
@classmethod
def create(cls, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str) -> "SolidLanguageServer":
def create(cls, config: LanguageServerConfig, repository_root_path: str) -> "SolidLanguageServer":
match config.code_language:
# Existing cases...
case Language.NEW_LANGUAGE:
from solidlsp.language_servers.new_language_server import NewLanguageServer
return NewLanguageServer(config, logger, repository_root_path)
return NewLanguageServer(config, repository_root_path)
```
## Step 3: Test Repository
-5
View File
@@ -7,7 +7,6 @@ from sensai.util.logging import LogTime
from serena.constants import SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
@@ -21,7 +20,6 @@ class LanguageServerFactory:
ignored_patterns: list[str],
ls_timeout: float | None = None,
ls_specific_settings: dict | None = None,
log_level: int = logging.INFO,
trace_lsp_communication: bool = False,
):
self.project_root = project_root
@@ -29,7 +27,6 @@ class LanguageServerFactory:
self.ignored_patterns = ignored_patterns
self.ls_timeout = ls_timeout
self.ls_specific_settings = ls_specific_settings
self.log_level = log_level
self.trace_lsp_communication = trace_lsp_communication
def create_language_server(self, language: Language) -> SolidLanguageServer:
@@ -39,12 +36,10 @@ class LanguageServerFactory:
trace_lsp_communication=self.trace_lsp_communication,
encoding=self.encoding,
)
ls_logger = LanguageServerLogger(log_level=self.log_level)
log.info(f"Creating language server instance for {self.project_root}, language={language}.")
return SolidLanguageServer.create(
ls_config,
ls_logger,
self.project_root,
timeout=self.ls_timeout,
solidlsp_settings=SolidLSPSettings(
-1
View File
@@ -388,7 +388,6 @@ class Project(ToStringMixin):
ignored_patterns=self._ignored_patterns,
ls_timeout=ls_timeout,
ls_specific_settings=ls_specific_settings,
log_level=log_level,
trace_lsp_communication=trace_lsp_communication,
)
self.language_server_manager = LanguageServerManager.from_languages(self.project_config.languages, factory)
@@ -15,12 +15,13 @@ from overrides import override
from solidlsp.language_servers.common import quote_windows_path
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class ALLanguageServer(SolidLanguageServer):
"""
@@ -37,9 +38,7 @@ class ALLanguageServer(SolidLanguageServer):
- File opening requirement before symbol retrieval
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Initialize the AL Language Server.
@@ -58,7 +57,7 @@ class ALLanguageServer(SolidLanguageServer):
"""
# Setup runtime dependencies and get the language server command
# This will download the AL extension if needed
cmd = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
cmd = self._setup_runtime_dependencies(config, solidlsp_settings)
self._project_load_check_supported: bool = True
"""Whether the AL server supports the project load status check request.
@@ -68,17 +67,10 @@ class ALLanguageServer(SolidLanguageServer):
request fails, preventing repeated unsuccessful attempts.
"""
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
"al", # Language ID for LSP
solidlsp_settings,
)
super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "al", solidlsp_settings)
@classmethod
def _download_al_extension(cls, logger: LanguageServerLogger, url: str, target_dir: str) -> bool:
def _download_al_extension(cls, url: str, target_dir: str) -> bool:
"""
Download and extract the AL extension from VS Code marketplace.
@@ -99,7 +91,7 @@ class ALLanguageServer(SolidLanguageServer):
"""
try:
logger.log(f"Downloading AL extension from {url}", logging.INFO)
log.info(f"Downloading AL extension from {url}")
# Create target directory for the extension
os.makedirs(target_dir, exist_ok=True)
@@ -118,7 +110,7 @@ class ALLanguageServer(SolidLanguageServer):
temp_file = os.path.join(target_dir, "al_extension_temp.vsix")
total_size = int(response.headers.get("content-length", 0))
logger.log(f"Downloading {total_size / 1024 / 1024:.1f} MB...", logging.INFO)
log.info(f"Downloading {total_size / 1024 / 1024:.1f} MB...")
with open(temp_file, "wb") as f:
downloaded = 0
@@ -128,9 +120,9 @@ class ALLanguageServer(SolidLanguageServer):
downloaded += len(chunk)
if total_size > 0 and downloaded % (10 * 1024 * 1024) == 0: # Log progress every 10MB
progress = (downloaded / total_size) * 100
logger.log(f"Download progress: {progress:.1f}%", logging.INFO)
log.info(f"Download progress: {progress:.1f}%")
logger.log("Download complete, extracting...", logging.INFO)
log.info("Download complete, extracting...")
# Extract VSIX file (VSIX files are just ZIP archives with a different extension)
# This will extract the extension folder containing the language server binaries
@@ -140,17 +132,15 @@ class ALLanguageServer(SolidLanguageServer):
# Clean up temp file
os.remove(temp_file)
logger.log("AL extension extracted successfully", logging.INFO)
log.info("AL extension extracted successfully")
return True
except Exception as e:
logger.log(f"Error downloading/extracting AL extension: {e}", logging.ERROR)
log.error(f"Error downloading/extracting AL extension: {e}")
return False
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> str:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
"""
Setup runtime dependencies for AL Language Server and return the command to start the server.
@@ -168,10 +158,10 @@ class ALLanguageServer(SolidLanguageServer):
system = platform.system()
# Find existing extension or download if needed
extension_path = cls._find_al_extension(logger, solidlsp_settings)
extension_path = cls._find_al_extension(solidlsp_settings)
if extension_path is None:
logger.log("AL extension not found on disk, attempting to download...", logging.INFO)
extension_path = cls._download_and_install_al_extension(logger, solidlsp_settings)
log.info("AL extension not found on disk, attempting to download...")
extension_path = cls._download_and_install_al_extension(solidlsp_settings)
if extension_path is None:
raise RuntimeError(
@@ -188,10 +178,10 @@ class ALLanguageServer(SolidLanguageServer):
raise RuntimeError(f"AL Language Server executable not found at: {executable_path}")
# Prepare and return the executable command
return cls._prepare_executable(executable_path, system, logger)
return cls._prepare_executable(executable_path, system)
@classmethod
def _find_al_extension(cls, logger: LanguageServerLogger, solidlsp_settings: SolidLSPSettings) -> str | None:
def _find_al_extension(cls, solidlsp_settings: SolidLSPSettings) -> str | None:
"""
Find AL extension in various locations.
@@ -207,28 +197,28 @@ class ALLanguageServer(SolidLanguageServer):
# Check environment variable
env_path = os.environ.get("AL_EXTENSION_PATH")
if env_path and os.path.exists(env_path):
logger.log(f"Found AL extension via AL_EXTENSION_PATH: {env_path}", logging.DEBUG)
log.debug(f"Found AL extension via AL_EXTENSION_PATH: {env_path}")
return env_path
elif env_path:
logger.log(f"AL_EXTENSION_PATH set but directory not found: {env_path}", logging.WARNING)
log.warning(f"AL_EXTENSION_PATH set but directory not found: {env_path}")
# Check default download location
default_path = os.path.join(cls.ls_resources_dir(solidlsp_settings), "al-extension", "extension")
if os.path.exists(default_path):
logger.log(f"Found AL extension in default location: {default_path}", logging.DEBUG)
log.debug(f"Found AL extension in default location: {default_path}")
return default_path
# Search VS Code extensions
vscode_path = cls._find_al_extension_in_vscode(logger)
vscode_path = cls._find_al_extension_in_vscode()
if vscode_path:
logger.log(f"Found AL extension in VS Code: {vscode_path}", logging.DEBUG)
log.debug(f"Found AL extension in VS Code: {vscode_path}")
return vscode_path
logger.log("AL extension not found in any known location", logging.DEBUG)
log.debug("AL extension not found in any known location")
return None
@classmethod
def _download_and_install_al_extension(cls, logger: LanguageServerLogger, solidlsp_settings: SolidLSPSettings) -> str | None:
def _download_and_install_al_extension(cls, solidlsp_settings: SolidLSPSettings) -> str | None:
"""
Download and install AL extension from VS Code marketplace.
@@ -242,17 +232,17 @@ class ALLanguageServer(SolidLanguageServer):
AL_VERSION = "latest"
url = f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-dynamics-smb/vsextensions/al/{AL_VERSION}/vspackage"
logger.log(f"Downloading AL extension from: {url}", logging.INFO)
log.info(f"Downloading AL extension from: {url}")
if cls._download_al_extension(logger, url, al_extension_dir):
if cls._download_al_extension(url, al_extension_dir):
extension_path = os.path.join(al_extension_dir, "extension")
if os.path.exists(extension_path):
logger.log("AL extension downloaded and installed successfully", logging.INFO)
log.info("AL extension downloaded and installed successfully")
return extension_path
else:
logger.log(f"Download completed but extension not found at: {extension_path}", logging.ERROR)
log.error(f"Download completed but extension not found at: {extension_path}")
else:
logger.log("Failed to download AL extension from marketplace", logging.ERROR)
log.error("Failed to download AL extension from marketplace")
return None
@@ -279,7 +269,7 @@ class ALLanguageServer(SolidLanguageServer):
raise RuntimeError(f"Unsupported platform: {system}")
@classmethod
def _prepare_executable(cls, executable_path: str, system: str, logger: LanguageServerLogger) -> str:
def _prepare_executable(cls, executable_path: str, system: str) -> str:
"""
Prepare the executable by setting permissions and handling path quoting.
@@ -296,16 +286,16 @@ class ALLanguageServer(SolidLanguageServer):
if system in ["Linux", "Darwin"]:
st = os.stat(executable_path)
os.chmod(executable_path, st.st_mode | stat.S_IEXEC)
logger.log(f"Set execute permission on: {executable_path}", logging.DEBUG)
log.debug(f"Set execute permission on: {executable_path}")
logger.log(f"Using AL Language Server executable: {executable_path}", logging.INFO)
log.info(f"Using AL Language Server executable: {executable_path}")
# The AL Language Server uses stdio for LSP communication by default
# Use the utility function to handle Windows path quoting
return quote_windows_path(executable_path)
@classmethod
def _get_language_server_command_fallback(cls, logger: LanguageServerLogger) -> str:
def _get_language_server_command_fallback(cls) -> str:
"""
Get the command to start the AL language server.
@@ -333,10 +323,10 @@ class ALLanguageServer(SolidLanguageServer):
if potential_extension:
al_extension_path = str(potential_extension)
logger.log(f"Found AL extension in current directory: {al_extension_path}", logging.DEBUG)
log.debug(f"Found AL extension in current directory: {al_extension_path}")
else:
# Try to find in common VS Code extension locations
al_extension_path = cls._find_al_extension_in_vscode(logger)
al_extension_path = cls._find_al_extension_in_vscode()
if not al_extension_path:
raise RuntimeError(
@@ -368,14 +358,14 @@ class ALLanguageServer(SolidLanguageServer):
st = os.stat(executable)
os.chmod(executable, st.st_mode | stat.S_IEXEC)
logger.log(f"Using AL Language Server executable: {executable}", logging.INFO)
log.info(f"Using AL Language Server executable: {executable}")
# The AL Language Server uses stdio for LSP communication (no --stdio flag needed)
# Use the utility function to handle Windows path quoting
return quote_windows_path(executable)
@classmethod
def _find_al_extension_in_vscode(cls, logger: LanguageServerLogger) -> str | None:
def _find_al_extension_in_vscode(cls) -> str | None:
"""
Try to find AL extension in common VS Code extension locations.
@@ -407,11 +397,11 @@ class ALLanguageServer(SolidLanguageServer):
for base_path in possible_paths:
if base_path.exists():
logger.log(f"Searching for AL extension in: {base_path}", logging.DEBUG)
log.debug(f"Searching for AL extension in: {base_path}")
# Look for AL extension directories
for item in base_path.iterdir():
if item.is_dir() and item.name.startswith("ms-dynamics-smb.al-"):
logger.log(f"Found AL extension at: {item}", logging.DEBUG)
log.debug(f"Found AL extension at: {item}")
return str(item)
return None
@@ -499,18 +489,18 @@ class ALLanguageServer(SolidLanguageServer):
def do_nothing(params: str) -> None:
return
def window_log_message(msg: str) -> None:
self.logger.log(f"AL LSP: window/logMessage: {msg}", logging.INFO)
def window_log_message(msg: dict) -> None:
log.info(f"AL LSP: window/logMessage: {msg}")
def publish_diagnostics(params: dict) -> None:
# AL server publishes diagnostics during initialization
uri = params.get("uri", "")
diagnostics = params.get("diagnostics", [])
self.logger.log(f"AL LSP: Diagnostics for {uri}: {len(diagnostics)} issues", logging.DEBUG)
log.debug(f"AL LSP: Diagnostics for {uri}: {len(diagnostics)} issues")
def handle_al_notifications(params: dict) -> None:
# AL server sends custom notifications during project loading
self.logger.log("AL LSP: Notification received", logging.DEBUG)
log.debug("AL LSP: Notification received")
# Register handlers for AL-specific notifications
# These notifications are sent by the AL server during initialization and operation
@@ -520,27 +510,24 @@ class ALLanguageServer(SolidLanguageServer):
self.server.on_notification("al/refreshExplorerObjects", handle_al_notifications) # AL-specific object updates
# Start the server process
self.logger.log("Starting AL Language Server process", logging.INFO)
log.info("Starting AL Language Server process")
self.server.start()
# Send initialize request
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to AL LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to AL LSP server and awaiting response")
# Send initialize and wait for response
resp = self.server.send_request("initialize", initialize_params)
if resp is None:
raise RuntimeError("AL Language Server initialization failed - no response")
self.logger.log("AL Language Server initialized successfully", logging.INFO)
log.info("AL Language Server initialized successfully")
# Send initialized notification
self.server.send_notification("initialized", {})
self.logger.log("Sent initialized notification", logging.INFO)
log.info("Sent initialized notification")
@override
def start(self) -> "ALLanguageServer":
@@ -598,9 +585,9 @@ class ALLanguageServer(SolidLanguageServer):
}
},
)
self.logger.log("Sent workspace configuration", logging.DEBUG)
log.debug("Sent workspace configuration")
except Exception as e:
self.logger.log(f"Failed to send workspace config: {e}", logging.WARNING)
log.warning(f"Failed to send workspace config: {e}")
# Check if app.json exists and open it
# app.json is the AL project manifest file (similar to package.json for Node.js)
@@ -620,9 +607,9 @@ class ALLanguageServer(SolidLanguageServer):
{"textDocument": {"uri": app_json_uri, "languageId": "json", "version": 1, "text": app_json_content}},
)
self.logger.log(f"Opened app.json: {app_json_uri}", logging.DEBUG)
log.debug(f"Opened app.json: {app_json_uri}")
except Exception as e:
self.logger.log(f"Failed to open app.json: {e}", logging.WARNING)
log.warning(f"Failed to open app.json: {e}")
# Try to set active workspace (AL-specific custom LSP request)
# This is optional and may not be supported by all AL server versions
@@ -639,10 +626,10 @@ class ALLanguageServer(SolidLanguageServer):
"timeout": 2, # Quick timeout since this is optional
},
)
self.logger.log(f"Set active workspace result: {result}", logging.DEBUG)
log.debug(f"Set active workspace result: {result}")
except Exception as e:
# This is a custom AL request, not critical if it fails
self.logger.log(f"Failed to set active workspace (non-critical): {e}", logging.DEBUG)
log.debug(f"Failed to set active workspace (non-critical): {e}")
# Check if project supports load status check (optional)
# Many AL server versions don't support this, so we use a short timeout
@@ -704,7 +691,7 @@ class ALLanguageServer(SolidLanguageServer):
Full symbol tree with all AL symbols from opened files organized by directory
"""
self.logger.log("AL: Starting request_full_symbol_tree with file opening", logging.DEBUG)
log.debug("AL: Starting request_full_symbol_tree with file opening")
# Determine the root path for scanning
if within_relative_path is not None:
@@ -743,10 +730,10 @@ class ALLanguageServer(SolidLanguageServer):
# File is outside repository root, skip it
continue
self.logger.log(f"AL: Found {len(al_files)} AL files", logging.DEBUG)
log.debug(f"AL: Found {len(al_files)} AL files")
if not al_files:
self.logger.log("AL: No AL files found in repository", logging.WARNING)
log.warning("AL: No AL files found in repository")
return []
# Collect all symbols from all files
@@ -756,7 +743,7 @@ class ALLanguageServer(SolidLanguageServer):
for file_path, relative_path in al_files:
try:
# Use our overridden request_document_symbols which handles opening
self.logger.log(f"AL: Getting symbols for {relative_path}", logging.DEBUG)
log.debug(f"AL: Getting symbols for {relative_path}")
all_syms, root_syms = self.request_document_symbols(relative_path).get_all_symbols_and_roots()
if root_syms:
@@ -773,7 +760,7 @@ class ALLanguageServer(SolidLanguageServer):
},
}
all_file_symbols.append(file_symbol)
self.logger.log(f"AL: Added {len(root_syms)} symbols from {relative_path}", logging.DEBUG)
log.debug(f"AL: Added {len(root_syms)} symbols from {relative_path}")
elif all_syms:
# If we only got all_syms but not root, use all_syms
file_symbol = {
@@ -788,13 +775,13 @@ class ALLanguageServer(SolidLanguageServer):
},
}
all_file_symbols.append(file_symbol)
self.logger.log(f"AL: Added {len(all_syms)} symbols from {relative_path}", logging.DEBUG)
log.debug(f"AL: Added {len(all_syms)} symbols from {relative_path}")
except Exception as e:
self.logger.log(f"AL: Failed to get symbols for {relative_path}: {e}", logging.WARNING)
log.warning(f"AL: Failed to get symbols for {relative_path}: {e}")
if all_file_symbols:
self.logger.log(f"AL: Returning symbols from {len(all_file_symbols)} files", logging.DEBUG)
log.debug(f"AL: Returning symbols from {len(all_file_symbols)} files")
# Group files by directory
directory_structure: dict[str, list] = {}
@@ -839,7 +826,7 @@ class ALLanguageServer(SolidLanguageServer):
return result
else:
self.logger.log("AL: No symbols found in any files", logging.WARNING)
log.warning("AL: No symbols found in any files")
return []
# ===== Phase 1: Custom AL Command Implementations =====
@@ -862,10 +849,10 @@ class ALLanguageServer(SolidLanguageServer):
try:
# Use custom AL command instead of standard LSP
response = self.server.send_request("al/gotodefinition", al_params)
self.logger.log(f"AL gotodefinition response: {response}", logging.DEBUG)
log.debug(f"AL gotodefinition response: {response}")
return response # type: ignore[return-value]
except Exception as e:
self.logger.log(f"Failed to use al/gotodefinition, falling back to standard: {e}", logging.WARNING)
log.warning(f"Failed to use al/gotodefinition, falling back to standard: {e}")
# Fallback to standard LSP method if custom command fails
return super()._send_definition_request(definition_params)
@@ -883,7 +870,7 @@ class ALLanguageServer(SolidLanguageServer):
"""
if not hasattr(self, "server") or not self.server_started:
self.logger.log("Cannot check project load - server not started", logging.DEBUG)
log.debug("Cannot check project load - server not started")
return False
# Check if we've already determined this request isn't supported
@@ -900,15 +887,15 @@ class ALLanguageServer(SolidLanguageServer):
return response.get("loaded", False)
elif response is None:
# None typically means the project is still loading
self.logger.log("Project load check returned None", logging.DEBUG)
log.debug("Project load check returned None")
return False
else:
self.logger.log(f"Unexpected response type for project load check: {type(response)}", logging.DEBUG)
log.debug(f"Unexpected response type for project load check: {type(response)}")
return False
except Exception as e:
# Mark as unsupported to avoid repeated failed attempts
self._project_load_check_supported = False
self.logger.log(f"Project load check not supported by this AL server version: {e}", logging.DEBUG)
log.debug(f"Project load check not supported by this AL server version: {e}")
# Assume loaded if we can't check
return True
@@ -928,16 +915,16 @@ class ALLanguageServer(SolidLanguageServer):
"""
start_time = time.time()
self.logger.log(f"Checking AL project load status (timeout: {timeout}s)...", logging.DEBUG)
log.debug(f"Checking AL project load status (timeout: {timeout}s)...")
while time.time() - start_time < timeout:
if self.check_project_loaded():
elapsed = time.time() - start_time
self.logger.log(f"AL project fully loaded after {elapsed:.1f}s", logging.INFO)
log.info(f"AL project fully loaded after {elapsed:.1f}s")
return True
time.sleep(0.5)
self.logger.log(f"Project load check timed out after {timeout}s (non-critical)", logging.DEBUG)
log.debug(f"Project load check timed out after {timeout}s (non-critical)")
return False
def set_active_workspace(self, workspace_uri: str | None = None) -> None:
@@ -956,7 +943,7 @@ class ALLanguageServer(SolidLanguageServer):
"""
if not hasattr(self, "server") or not self.server_started:
self.logger.log("Cannot set active workspace - server not started", logging.DEBUG)
log.debug("Cannot set active workspace - server not started")
return
if workspace_uri is None:
@@ -966,7 +953,7 @@ class ALLanguageServer(SolidLanguageServer):
try:
self.server.send_request("al/setActiveWorkspace", params)
self.logger.log(f"Set active workspace to: {workspace_uri}", logging.INFO)
log.info(f"Set active workspace to: {workspace_uri}")
except Exception as e:
self.logger.log(f"Failed to set active workspace: {e}", logging.WARNING)
log.warning(f"Failed to set active workspace: {e}")
# Non-critical error, continue operation
@@ -12,11 +12,12 @@ import threading
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class BashLanguageServer(SolidLanguageServer):
"""
@@ -24,17 +25,14 @@ class BashLanguageServer(SolidLanguageServer):
Contains various configurations and settings specific to Bash scripting.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a BashLanguageServer instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
bash_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
bash_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=bash_lsp_executable_path, cwd=repository_root_path),
"bash",
@@ -44,9 +42,7 @@ class BashLanguageServer(SolidLanguageServer):
self.initialize_searcher_command_available = threading.Event()
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> str:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
"""
Setup runtime dependencies for Bash Language Server and return the command to start the server.
"""
@@ -76,9 +72,9 @@ class BashLanguageServer(SolidLanguageServer):
bash_executable_path += ".cmd"
if not os.path.exists(bash_executable_path):
logger.log(f"Bash Language Server executable not found at {bash_executable_path}. Installing...", logging.INFO)
deps.install(logger, bash_ls_dir)
logger.log("Bash language server dependencies installed successfully", logging.INFO)
log.info(f"Bash Language Server executable not found at {bash_executable_path}. Installing...")
deps.install(bash_ls_dir)
log.info("Bash language server dependencies installed successfully")
if not os.path.exists(bash_executable_path):
raise FileNotFoundError(
@@ -146,11 +142,11 @@ class BashLanguageServer(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
# Check for bash-language-server ready signals
message_text = msg.get("message", "")
if "Analyzing" in message_text or "analysis complete" in message_text.lower():
self.logger.log("Bash language server analysis signals detected", logging.INFO)
log.info("Bash language server analysis signals detected")
self.server_ready.set()
self.completions_available.set()
@@ -160,16 +156,13 @@ class BashLanguageServer(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Bash server process", logging.INFO)
log.info("Starting Bash server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
self.logger.log(f"Received initialize response from bash server: {init_response}", logging.DEBUG)
log.debug(f"Received initialize response from bash server: {init_response}")
# Enhanced capability checks for bash-language-server 5.6.0
assert init_response["capabilities"]["textDocumentSync"] in [1, 2] # Full or Incremental
@@ -177,22 +170,22 @@ class BashLanguageServer(SolidLanguageServer):
# Verify document symbol support is available
if "documentSymbolProvider" in init_response["capabilities"]:
self.logger.log("Bash server supports document symbols", logging.INFO)
log.info("Bash server supports document symbols")
else:
self.logger.log("Warning: Bash server does not report document symbol support", logging.WARNING)
log.warning("Warning: Bash server does not report document symbol support")
self.server.notify.initialized({})
# Wait for server readiness with timeout
self.logger.log("Waiting for Bash language server to be ready...", logging.INFO)
log.info("Waiting for Bash language server to be ready...")
if not self.server_ready.wait(timeout=3.0):
# Fallback: assume server is ready after timeout
# This is common. bash-language-server doesn't always send explicit ready signals. Log as info
self.logger.log("Timeout waiting for bash server ready signal, proceeding anyway", logging.INFO)
log.info("Timeout waiting for bash server ready signal, proceeding anyway")
self.server_ready.set()
self.completions_available.set()
else:
self.logger.log("Bash server initialization complete", logging.INFO)
log.info("Bash server initialization complete")
def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols:
# Uses the standard LSP documentSymbol request which provides reliable function detection
@@ -202,16 +195,13 @@ class BashLanguageServer(SolidLanguageServer):
# - Functions with various indentation levels
# - Functions with comments before/after/inside
self.logger.log(f"Requesting document symbols via LSP for {relative_file_path}", logging.DEBUG)
log.debug(f"Requesting document symbols via LSP for {relative_file_path}")
# Use the standard LSP approach - bash-language-server handles all function syntaxes correctly
document_symbols = super().request_document_symbols(relative_file_path, file_buffer=file_buffer)
# Log detection results for debugging
functions = [s for s in document_symbols.iter_symbols() if s.get("kind") == 12]
self.logger.log(
f"LSP function detection for {relative_file_path}: Found {len(functions)} functions",
logging.INFO,
)
log.info(f"LSP function detection for {relative_file_path}: Found {len(functions)} functions")
return document_symbols
@@ -10,13 +10,14 @@ from typing import Any, cast
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
class ClangdLanguageServer(SolidLanguageServer):
"""
@@ -25,20 +26,13 @@ class ClangdLanguageServer(SolidLanguageServer):
Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a ClangdLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
clangd_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
clangd_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=clangd_executable_path, cwd=repository_root_path),
"cpp",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=clangd_executable_path, cwd=repository_root_path), "cpp", solidlsp_settings
)
self.server_ready = threading.Event()
self.service_ready_event = threading.Event()
@@ -46,9 +40,7 @@ class ClangdLanguageServer(SolidLanguageServer):
self.resolve_main_method_available = threading.Event()
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> str:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
"""
Setup runtime dependencies for ClangdLanguageServer and return the command to start the server.
"""
@@ -110,16 +102,13 @@ class ClangdLanguageServer(SolidLanguageServer):
+ " Arch Linux: sudo pacman -S clang\n"
+ "See https://clangd.llvm.org/installation for more details."
)
logger.log(f"Using system-installed clangd at {clangd_executable_path}", logging.INFO)
log.info(f"Using system-installed clangd at {clangd_executable_path}")
else:
# Standard download and install for platforms with prebuilt binaries
clangd_executable_path = deps.binary_path(clangd_ls_dir)
if not os.path.exists(clangd_executable_path):
logger.log(
f"Clangd executable not found at {clangd_executable_path}. Downloading from {dep.url}",
logging.INFO,
)
_ = deps.install(logger, clangd_ls_dir)
log.info(f"Clangd executable not found at {clangd_executable_path}. Downloading from {dep.url}")
_ = deps.install(clangd_ls_dir)
if not os.path.exists(clangd_executable_path):
raise FileNotFoundError(
f"Clangd executable not found at {clangd_executable_path}.\n"
@@ -197,7 +186,7 @@ class ClangdLanguageServer(SolidLanguageServer):
self.server_ready.set()
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_notification("language/status", lang_status_handler)
@@ -208,14 +197,11 @@ class ClangdLanguageServer(SolidLanguageServer):
self.server.on_notification("language/actionableNotification", do_nothing)
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.logger.log("Starting Clangd server process", logging.INFO)
log.info("Starting Clangd server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" in init_response["capabilities"]
+10 -18
View File
@@ -12,13 +12,14 @@ from typing import cast
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
def run_command(cmd: list, capture_output: bool = True) -> subprocess.CompletedProcess:
return subprocess.run(
@@ -86,16 +87,13 @@ class ClojureLSP(SolidLanguageServer):
]
)
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a ClojureLSP instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
clojure_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
clojure_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=clojure_lsp_executable_path, cwd=repository_root_path),
"clojure",
@@ -107,9 +105,7 @@ class ClojureLSP(SolidLanguageServer):
self.service_ready_event = threading.Event()
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> str:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
"""Setup runtime dependencies for clojure-lsp and return the command to start the server."""
verify_clojure_cli()
deps = ClojureLSP.runtime_dependencies
@@ -118,11 +114,10 @@ class ClojureLSP(SolidLanguageServer):
clojurelsp_ls_dir = cls.ls_resources_dir(solidlsp_settings)
clojurelsp_executable_path = deps.binary_path(clojurelsp_ls_dir)
if not os.path.exists(clojurelsp_executable_path):
logger.log(
log.info(
f"Downloading and extracting clojure-lsp from {dependency.url} to {clojurelsp_ls_dir}",
logging.INFO,
)
deps.install(logger, clojurelsp_ls_dir)
deps.install(clojurelsp_ls_dir)
if not os.path.exists(clojurelsp_executable_path):
raise FileNotFoundError(f"Download failed? Could not find clojure-lsp executable at {clojurelsp_executable_path}")
os.chmod(clojurelsp_executable_path, 0o755)
@@ -189,7 +184,7 @@ class ClojureLSP(SolidLanguageServer):
self.server_ready.set()
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_notification("language/status", lang_status_handler)
@@ -200,15 +195,12 @@ class ClojureLSP(SolidLanguageServer):
self.server.on_notification("language/actionableNotification", do_nothing)
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.logger.log("Starting clojure-lsp server process", logging.INFO)
log.info("Starting clojure-lsp server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" in init_response["capabilities"]
+5 -6
View File
@@ -8,7 +8,6 @@ from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass, replace
from typing import Any, cast
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import FileUtils, PlatformUtils
from solidlsp.util.subprocess_util import subprocess_kwargs
@@ -79,7 +78,7 @@ class RuntimeDependencyCollection:
return target_dir
return os.path.join(target_dir, dep.binary_name)
def install(self, logger: LanguageServerLogger, target_dir: str) -> dict[str, str]:
def install(self, target_dir: str) -> dict[str, str]:
"""Install all dependencies for the current platform into *target_dir*.
Returns a mapping from dependency id to the resolved binary path.
@@ -88,7 +87,7 @@ class RuntimeDependencyCollection:
results: dict[str, str] = {}
for dep in self.get_dependencies_for_current_platform():
if dep.url:
self._install_from_url(dep, logger, target_dir)
self._install_from_url(dep, target_dir)
if dep.command:
self._run_command(dep.command, target_dir)
if dep.binary_name:
@@ -131,15 +130,15 @@ class RuntimeDependencyCollection:
)
@staticmethod
def _install_from_url(dep: RuntimeDependency, logger: LanguageServerLogger, target_dir: str) -> None:
def _install_from_url(dep: RuntimeDependency, target_dir: str) -> None:
if not dep.url:
raise ValueError(f"Dependency {dep.id} has no URL")
if dep.archive_type in ("gz", "binary") and dep.binary_name:
dest = os.path.join(target_dir, dep.binary_name)
FileUtils.download_and_extract_archive(logger, dep.url, dest, dep.archive_type)
FileUtils.download_and_extract_archive(dep.url, dest, dep.archive_type)
else:
FileUtils.download_and_extract_archive(logger, dep.url, target_dir, dep.archive_type or "zip")
FileUtils.download_and_extract_archive(dep.url, target_dir, dep.archive_type or "zip")
def quote_windows_path(path: str) -> str:
@@ -21,7 +21,6 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PathUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, InitializeResult
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -30,6 +29,8 @@ from solidlsp.util.zip import SafeZipExtractor
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
_RUNTIME_DEPENDENCIES = [
RuntimeDependency(
id="CSharpLanguageServer",
@@ -198,14 +199,12 @@ class CSharpLanguageServer(SolidLanguageServer):
See the `_RUNTIME_DEPENDENCIES` variable above for the available dependency ids and platform_ids.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a CSharpLanguageServer instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
dotnet_path, language_server_path = self._ensure_server_installed(logger, config, solidlsp_settings)
dotnet_path, language_server_path = self._ensure_server_installed(config, solidlsp_settings)
# Find solution or project file
solution_or_project = find_solution_or_project_file(repository_root_path)
@@ -219,20 +218,13 @@ class CSharpLanguageServer(SolidLanguageServer):
# The language server will discover the solution/project from the workspace root
if solution_or_project:
logger.log(f"Found solution/project file: {solution_or_project}", logging.INFO)
log.info(f"Found solution/project file: {solution_or_project}")
else:
logger.log("No .sln or .csproj file found, language server will attempt auto-discovery", logging.WARNING)
log.warning("No .sln or .csproj file found, language server will attempt auto-discovery")
logger.log(f"Language server command: {' '.join(cmd)}", logging.DEBUG)
log.debug(f"Language server command: {' '.join(cmd)}")
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
"csharp",
solidlsp_settings,
)
super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp", solidlsp_settings)
self.initialization_complete = threading.Event()
@@ -241,9 +233,7 @@ class CSharpLanguageServer(SolidLanguageServer):
return super().is_ignored_dirname(dirname) or dirname in ["bin", "obj", "packages", ".vs"]
@classmethod
def _ensure_server_installed(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> tuple[str, str]:
def _ensure_server_installed(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> tuple[str, str]:
"""
Ensure .NET runtime and Microsoft.CodeAnalysis.LanguageServer are available.
Returns a tuple of (dotnet_path, language_server_dll_path).
@@ -251,30 +241,27 @@ class CSharpLanguageServer(SolidLanguageServer):
language_specific_config = solidlsp_settings.get_ls_specific_settings(cls.get_language_enum_instance())
runtime_dependency_overrides = cast(list[dict[str, Any]], language_specific_config.get("runtime_dependencies", []))
logger.log("Resolving runtime dependencies", logging.DEBUG)
log.debug("Resolving runtime dependencies")
runtime_dependencies = RuntimeDependencyCollection(
_RUNTIME_DEPENDENCIES,
overrides=runtime_dependency_overrides,
)
logger.log(
log.debug(
f"Available runtime dependencies: {runtime_dependencies.get_dependencies_for_current_platform}",
logging.DEBUG,
)
# Find the dependencies for our platform
lang_server_dep = runtime_dependencies.get_single_dep_for_current_platform("CSharpLanguageServer")
dotnet_runtime_dep = runtime_dependencies.get_single_dep_for_current_platform("DotNetRuntime")
dotnet_path = CSharpLanguageServer._ensure_dotnet_runtime(logger, dotnet_runtime_dep, solidlsp_settings)
server_dll_path = CSharpLanguageServer._ensure_language_server(logger, lang_server_dep, solidlsp_settings)
dotnet_path = CSharpLanguageServer._ensure_dotnet_runtime(dotnet_runtime_dep, solidlsp_settings)
server_dll_path = CSharpLanguageServer._ensure_language_server(lang_server_dep, solidlsp_settings)
return dotnet_path, server_dll_path
@classmethod
def _ensure_dotnet_runtime(
cls, logger: LanguageServerLogger, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
) -> str:
def _ensure_dotnet_runtime(cls, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings) -> str:
"""Ensure .NET runtime is available and return the dotnet executable path."""
# TODO: use RuntimeDependency util methods instead of custom validation/download logic
@@ -285,18 +272,16 @@ class CSharpLanguageServer(SolidLanguageServer):
try:
result = subprocess.run([system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
if "Microsoft.NETCore.App 9." in result.stdout:
logger.log("Found system .NET 9 runtime", logging.INFO)
log.info("Found system .NET 9 runtime")
return system_dotnet
except subprocess.CalledProcessError:
pass
# Download .NET 9 runtime using config
return cls._ensure_dotnet_runtime_from_config(logger, dotnet_runtime_dep, solidlsp_settings)
return cls._ensure_dotnet_runtime_from_config(dotnet_runtime_dep, solidlsp_settings)
@classmethod
def _ensure_language_server(
cls, logger: LanguageServerLogger, lang_server_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
) -> str:
def _ensure_language_server(cls, lang_server_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings) -> str:
"""Ensure language server is available and return the DLL path."""
package_name = lang_server_dep.package_name
package_version = lang_server_dep.package_version
@@ -306,14 +291,14 @@ class CSharpLanguageServer(SolidLanguageServer):
server_dll = server_dir / lang_server_dep.binary_name
if server_dll.exists():
logger.log(f"Using cached Microsoft.CodeAnalysis.LanguageServer from {server_dll}", logging.INFO)
log.info(f"Using cached Microsoft.CodeAnalysis.LanguageServer from {server_dll}")
return str(server_dll)
# Download and install the language server
logger.log(f"Downloading {package_name} version {package_version}...", logging.INFO)
log.info(f"Downloading {package_name} version {package_version}...")
assert package_version is not None
assert package_name is not None
package_path = cls._download_nuget_package_direct(logger, package_name, package_version, solidlsp_settings)
package_path = cls._download_nuget_package_direct(package_name, package_version, solidlsp_settings)
# Extract and install
cls._extract_language_server(lang_server_dep, package_path, server_dir)
@@ -325,7 +310,7 @@ class CSharpLanguageServer(SolidLanguageServer):
if platform.system().lower() != "windows":
server_dll.chmod(0o755)
logger.log(f"Successfully installed Microsoft.CodeAnalysis.LanguageServer to {server_dll}", logging.INFO)
log.info(f"Successfully installed Microsoft.CodeAnalysis.LanguageServer to {server_dll}")
return str(server_dll)
@staticmethod
@@ -352,9 +337,7 @@ class CSharpLanguageServer(SolidLanguageServer):
shutil.copytree(source_dir, server_dir, dirs_exist_ok=True)
@classmethod
def _download_nuget_package_direct(
cls, logger: LanguageServerLogger, package_name: str, package_version: str, solidlsp_settings: SolidLSPSettings
) -> Path:
def _download_nuget_package_direct(cls, package_name: str, package_version: str, solidlsp_settings: SolidLSPSettings) -> Path:
"""
Download a NuGet package directly from the Azure NuGet feed.
Returns the path to the extracted package directory.
@@ -367,7 +350,7 @@ class CSharpLanguageServer(SolidLanguageServer):
try:
# First, get the service index from the Azure feed
logger.log("Fetching NuGet service index from Azure feed...", logging.DEBUG)
log.debug("Fetching NuGet service index from Azure feed...")
with urllib.request.urlopen(azure_feed_url) as response:
service_index = json.loads(response.read().decode())
@@ -386,7 +369,7 @@ class CSharpLanguageServer(SolidLanguageServer):
package_version_lower = package_version.lower()
package_url = f"{package_base_address.rstrip('/')}/{package_id_lower}/{package_version_lower}/{package_id_lower}.{package_version_lower}.nupkg"
logger.log(f"Downloading package from: {package_url}", logging.DEBUG)
log.debug(f"Downloading package from: {package_url}")
# Download the .nupkg file
nupkg_file = temp_dir / f"{package_name}.{package_version}.nupkg"
@@ -403,7 +386,7 @@ class CSharpLanguageServer(SolidLanguageServer):
# Clean up the nupkg file
nupkg_file.unlink()
logger.log(f"Successfully downloaded and extracted {package_name} version {package_version}", logging.INFO)
log.info(f"Successfully downloaded and extracted {package_name} version {package_version}")
return package_extract_dir
except Exception as e:
@@ -412,9 +395,7 @@ class CSharpLanguageServer(SolidLanguageServer):
) from e
@classmethod
def _ensure_dotnet_runtime_from_config(
cls, logger: LanguageServerLogger, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
) -> str:
def _ensure_dotnet_runtime_from_config(cls, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings) -> str:
"""
Ensure .NET 9 runtime is available using runtime dependency configuration.
Returns the path to the dotnet executable.
@@ -428,7 +409,7 @@ class CSharpLanguageServer(SolidLanguageServer):
try:
result = subprocess.run([system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
if "Microsoft.NETCore.App 9." in result.stdout:
logger.log("Found system .NET 9 runtime", logging.INFO)
log.info("Found system .NET 9 runtime")
return system_dotnet
except subprocess.CalledProcessError:
pass
@@ -439,17 +420,17 @@ class CSharpLanguageServer(SolidLanguageServer):
dotnet_exe = dotnet_dir / dotnet_runtime_dep.binary_name
if dotnet_exe.exists():
logger.log(f"Using cached .NET runtime from {dotnet_exe}", logging.INFO)
log.info(f"Using cached .NET runtime from {dotnet_exe}")
return str(dotnet_exe)
# Download .NET runtime
logger.log("Downloading .NET 9 runtime...", logging.INFO)
log.info("Downloading .NET 9 runtime...")
dotnet_dir.mkdir(parents=True, exist_ok=True)
custom_settings = solidlsp_settings.get_ls_specific_settings(cls.get_language_enum_instance())
custom_dotnet_runtime_url = custom_settings.get("dotnet_runtime_url")
if custom_dotnet_runtime_url is not None:
logger.log(f"Using custom .NET runtime url: {custom_dotnet_runtime_url}", logging.INFO)
log.info(f"Using custom .NET runtime url: {custom_dotnet_runtime_url}")
url = custom_dotnet_runtime_url
else:
url = dotnet_runtime_dep.url
@@ -459,7 +440,7 @@ class CSharpLanguageServer(SolidLanguageServer):
# Download the runtime
download_path = dotnet_dir / f"dotnet-runtime.{archive_type}"
try:
logger.log(f"Downloading from {url}", logging.DEBUG)
log.debug(f"Downloading from {url}")
urllib.request.urlretrieve(url, download_path)
# Extract the archive
@@ -478,7 +459,7 @@ class CSharpLanguageServer(SolidLanguageServer):
if platform.system().lower() != "windows":
dotnet_exe.chmod(0o755)
logger.log(f"Successfully installed .NET 9 runtime to {dotnet_exe}", logging.INFO)
log.info(f"Successfully installed .NET 9 runtime to {dotnet_exe}")
return str(dotnet_exe)
except Exception as e:
@@ -551,7 +532,7 @@ class CSharpLanguageServer(SolidLanguageServer):
# Map LSP message types to Python logging levels
level_map = {1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG} # Error # Warning # Info # Log
self.logger.log(f"LSP: {message_text}", level_map.get(level, logging.DEBUG))
log.info(f"LSP: {message_text}", level_map.get(level, logging.DEBUG))
def handle_progress(params: dict) -> None:
"""Handle progress notifications from the language server."""
@@ -559,7 +540,7 @@ class CSharpLanguageServer(SolidLanguageServer):
value = params.get("value", {})
# Log raw progress for debugging
self.logger.log(f"Progress notification received: {params}", logging.DEBUG)
log.debug(f"Progress notification received: {params}")
# Handle different progress notification types
kind = value.get("kind")
@@ -570,22 +551,22 @@ class CSharpLanguageServer(SolidLanguageServer):
percentage = value.get("percentage")
if percentage is not None:
self.logger.log(f"Progress [{token}]: {title} - {message} ({percentage}%)", logging.INFO)
log.debug(f"Progress [{token}]: {title} - {message} ({percentage}%)")
else:
self.logger.log(f"Progress [{token}]: {title} - {message}", logging.INFO)
log.info(f"Progress [{token}]: {title} - {message}")
elif kind == "report":
message = value.get("message", "")
percentage = value.get("percentage")
if percentage is not None:
self.logger.log(f"Progress [{token}]: {message} ({percentage}%)", logging.INFO)
log.info(f"Progress [{token}]: {message} ({percentage}%)")
elif message:
self.logger.log(f"Progress [{token}]: {message}", logging.INFO)
log.info(f"Progress [{token}]: {message}")
elif kind == "end":
message = value.get("message", "Operation completed")
self.logger.log(f"Progress [{token}]: {message}", logging.INFO)
log.info(f"Progress [{token}]: {message}")
def handle_workspace_configuration(params: dict) -> list:
"""Handle workspace/configuration requests from the server."""
@@ -655,21 +636,21 @@ class CSharpLanguageServer(SolidLanguageServer):
self.server.on_request("client/registerCapability", handle_register_capability)
self.server.on_request("workspace/_roslyn_projectNeedsRestore", handle_project_needs_restore)
self.logger.log("Starting Microsoft.CodeAnalysis.LanguageServer process", logging.INFO)
log.info("Starting Microsoft.CodeAnalysis.LanguageServer process")
try:
self.server.start()
except Exception as e:
self.logger.log(f"Failed to start language server process: {e}", logging.ERROR)
log.info(f"Failed to start language server process: {e}", logging.ERROR)
raise SolidLSPException(f"Failed to start C# language server: {e}")
# Send initialization
initialize_params = self._get_initialize_params()
self.logger.log("Sending initialize request to language server", logging.INFO)
log.info("Sending initialize request to language server")
try:
init_response = self.server.send.initialize(initialize_params)
self.logger.log(f"Received initialize response: {init_response}", logging.DEBUG)
log.info(f"Received initialize response: {init_response}")
except Exception as e:
raise SolidLSPException(f"Failed to initialize C# language server for {self.repository_root_path}: {e}") from e
@@ -700,11 +681,10 @@ class CSharpLanguageServer(SolidLanguageServer):
self.initialization_complete.set()
self.completions_available.set()
self.logger.log(
log.info(
"Microsoft.CodeAnalysis.LanguageServer initialized and ready\n"
"Waiting for language server to index project files...\n"
"This may take a while for large projects",
logging.INFO,
"This may take a while for large projects"
)
def _force_pull_diagnostics(self, init_response: dict | InitializeResult) -> None:
@@ -724,7 +704,7 @@ class CSharpLanguageServer(SolidLanguageServer):
"workspaceDiagnostics": True,
}
)
self.logger.log("Applied diagnostic capabilities hack for better C# diagnostics", logging.DEBUG)
log.debug("Applied diagnostic capabilities hack for better C# diagnostics")
def _open_solution_and_projects(self) -> None:
"""
@@ -741,7 +721,7 @@ class CSharpLanguageServer(SolidLanguageServer):
if solution_file:
solution_uri = PathUtils.path_to_uri(solution_file)
self.server.notify.send_notification("solution/open", {"solution": solution_uri})
self.logger.log(f"Opened solution file: {solution_file}", logging.INFO)
log.debug(f"Opened solution file: {solution_file}")
# Find and open project files
project_files = []
@@ -753,7 +733,7 @@ class CSharpLanguageServer(SolidLanguageServer):
if project_files:
project_uris = [PathUtils.path_to_uri(project_file) for project_file in project_files]
self.server.notify.send_notification("project/open", {"projects": project_uris})
self.logger.log(f"Opened project files: {project_files}", logging.DEBUG)
log.debug(f"Opened project files: {project_files}")
@override
def _get_wait_time_for_cross_file_referencing(self) -> float:
@@ -4,7 +4,6 @@ import pathlib
from typing import cast
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
@@ -12,30 +11,25 @@ from ..ls_config import LanguageServerConfig
from ..lsp_protocol_handler.lsp_types import InitializeParams
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
class DartLanguageServer(SolidLanguageServer):
"""
Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
) -> None:
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
"""
Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
executable_path = self._setup_runtime_dependencies(logger, solidlsp_settings)
executable_path = self._setup_runtime_dependencies(solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path),
"dart",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path), "dart", solidlsp_settings
)
@classmethod
def _setup_runtime_dependencies(cls, logger: "LanguageServerLogger", solidlsp_settings: SolidLSPSettings) -> str:
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
deps = RuntimeDependencyCollection(
[
RuntimeDependency(
@@ -85,7 +79,7 @@ class DartLanguageServer(SolidLanguageServer):
dart_executable_path = deps.binary_path(dart_ls_dir)
if not os.path.exists(dart_executable_path):
deps.install(logger, dart_ls_dir)
deps.install(dart_ls_dir)
assert os.path.exists(dart_executable_path)
os.chmod(dart_executable_path, 0o755)
@@ -136,7 +130,7 @@ class DartLanguageServer(SolidLanguageServer):
pass
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", do_nothing)
self.server.on_notification("language/status", do_nothing)
@@ -147,17 +141,11 @@ class DartLanguageServer(SolidLanguageServer):
self.server.on_notification("language/actionableNotification", do_nothing)
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.logger.log("Starting dart-language-server server process", logging.INFO)
log.info("Starting dart-language-server server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request to dart-language-server",
logging.DEBUG,
)
log.debug("Sending initialize request to dart-language-server")
init_response = self.server.send_request("initialize", initialize_params) # type: ignore
self.logger.log(
f"Received initialize response from dart-language-server: {init_response}",
logging.INFO,
)
log.info(f"Received initialize response from dart-language-server: {init_response}")
self.server.notify.initialized({})
+19 -30
View File
@@ -16,13 +16,14 @@ from overrides import override
from solidlsp.ls import LSPFileBuffer, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_types import UnifiedSymbolInformation
from solidlsp.ls_utils import FileUtils, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import DocumentSymbol, InitializeParams, SymbolInformation
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
@dataclasses.dataclass
class RuntimeDependencyPaths:
@@ -61,14 +62,12 @@ class EclipseJDTLS(SolidLanguageServer):
```
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a new EclipseJDTLS instance initializing the language server settings appropriately.
This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
runtime_dependency_paths = self._setupRuntimeDependencies(logger, config, solidlsp_settings)
runtime_dependency_paths = self._setupRuntimeDependencies(config, solidlsp_settings)
self.runtime_dependency_paths = runtime_dependency_paths
# ws_dir is the workspace directory for the EclipseJDTLS server
@@ -150,7 +149,7 @@ class EclipseJDTLS(SolidLanguageServer):
self.initialize_searcher_command_available = threading.Event()
super().__init__(
config, logger, repository_root_path, ProcessLaunchInfo(cmd, proc_env, proc_cwd), "java", solidlsp_settings=solidlsp_settings
config, repository_root_path, ProcessLaunchInfo(cmd, proc_env, proc_cwd), "java", solidlsp_settings=solidlsp_settings
)
@override
@@ -172,9 +171,7 @@ class EclipseJDTLS(SolidLanguageServer):
]
@classmethod
def _setupRuntimeDependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> RuntimeDependencyPaths:
def _setupRuntimeDependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> RuntimeDependencyPaths:
"""
Setup runtime dependencies for EclipseJDTLS and return the paths.
"""
@@ -266,7 +263,6 @@ class EclipseJDTLS(SolidLanguageServer):
if not os.path.exists(gradle_path):
FileUtils.download_and_extract_archive(
logger,
runtime_dependencies["gradle"]["platform-agnostic"]["url"],
str(PurePath(gradle_path).parent),
runtime_dependencies["gradle"]["platform-agnostic"]["archiveType"],
@@ -292,7 +288,7 @@ class EclipseJDTLS(SolidLanguageServer):
os.path.exists(jdtls_readonly_config_path),
]
):
FileUtils.download_and_extract_archive(logger, dependency["url"], vscode_java_path, dependency["archiveType"])
FileUtils.download_and_extract_archive(dependency["url"], vscode_java_path, dependency["archiveType"])
os.chmod(jre_path, 0o755)
@@ -315,7 +311,7 @@ class EclipseJDTLS(SolidLanguageServer):
os.path.exists(intellisense_members_path),
]
):
FileUtils.download_and_extract_archive(logger, dependency["url"], intellicode_directory_path, dependency["archiveType"])
FileUtils.download_and_extract_archive(dependency["url"], intellicode_directory_path, dependency["archiveType"])
assert os.path.exists(intellicode_directory_path)
assert os.path.exists(intellicode_jar_path)
@@ -355,18 +351,16 @@ class EclipseJDTLS(SolidLanguageServer):
f"Fix: create the file, update path in ~/.serena/serena_config.yml (ls_specific_settings -> java -> maven_user_settings), "
f"or remove the setting to use default ({default_maven_settings_path})"
)
self.logger.log(error_msg, logging.ERROR)
log.error(error_msg)
raise FileNotFoundError(error_msg)
maven_settings_path = custom_maven_settings_path
self.logger.log(f"Using Maven settings from custom location: {maven_settings_path}", logging.INFO)
log.info(f"Using Maven settings from custom location: {maven_settings_path}")
elif os.path.exists(default_maven_settings_path):
maven_settings_path = default_maven_settings_path
self.logger.log(f"Using Maven settings from default location: {maven_settings_path}", logging.INFO)
log.info(f"Using Maven settings from default location: {maven_settings_path}")
else:
maven_settings_path = None
self.logger.log(
f"Maven settings not found at default location ({default_maven_settings_path}), will use JDTLS defaults", logging.INFO
)
log.info(f"Maven settings not found at default location ({default_maven_settings_path}), will use JDTLS defaults")
# Gradle user home: default to ~/.gradle
default_gradle_home = os.path.join(os.path.expanduser("~"), ".gradle")
@@ -379,18 +373,16 @@ class EclipseJDTLS(SolidLanguageServer):
f"Fix: create the directory, update path in ~/.serena/serena_config.yml (ls_specific_settings -> java -> gradle_user_home), "
f"or remove the setting to use default (~/.gradle)"
)
self.logger.log(error_msg, logging.ERROR)
log.error(error_msg)
raise FileNotFoundError(error_msg)
gradle_user_home = custom_gradle_home
self.logger.log(f"Using Gradle user home from custom location: {gradle_user_home}", logging.INFO)
log.info(f"Using Gradle user home from custom location: {gradle_user_home}")
elif os.path.exists(default_gradle_home):
gradle_user_home = default_gradle_home
self.logger.log(f"Using Gradle user home from default location: {gradle_user_home}", logging.INFO)
log.info(f"Using Gradle user home from default location: {gradle_user_home}")
else:
gradle_user_home = None
self.logger.log(
f"Gradle user home not found at default location ({default_gradle_home}), will use JDTLS defaults", logging.INFO
)
log.info(f"Gradle user home not found at default location ({default_gradle_home}), will use JDTLS defaults")
initialize_params = {
"locale": "en",
@@ -745,7 +737,7 @@ class EclipseJDTLS(SolidLanguageServer):
return []
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params: dict) -> None:
return
@@ -758,14 +750,11 @@ class EclipseJDTLS(SolidLanguageServer):
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.server.on_notification("language/actionableNotification", do_nothing)
self.logger.log("Starting EclipseJDTLS server process", logging.INFO)
log.info("Starting EclipseJDTLS server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" not in init_response["capabilities"]
@@ -10,7 +10,6 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler import lsp_types
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
@@ -19,6 +18,8 @@ from solidlsp.settings import SolidLSPSettings
from ..common import RuntimeDependency
log = logging.getLogger(__name__)
class ElixirTools(SolidLanguageServer):
"""
@@ -78,7 +79,7 @@ class ElixirTools(SolidLanguageServer):
if isinstance(item, dict) and "uri" in item:
abs_path = PathUtils.uri_to_path(item["uri"])
if self._is_next_ls_internal_file(abs_path):
self.logger.log(f"Filtering out Next LS internal file: {abs_path}", logging.DEBUG)
log.debug(f"Filtering out Next LS internal file: {abs_path}")
continue
filtered_response.append(item)
@@ -96,9 +97,7 @@ class ElixirTools(SolidLanguageServer):
return None
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> str:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
"""
Setup runtime dependencies for Next LS.
Downloads the Next LS binary for the current platform and returns the path to the executable.
@@ -110,7 +109,7 @@ class ElixirTools(SolidLanguageServer):
"Elixir is not installed. Please install Elixir from https://elixir-lang.org/install.html and make sure it is added to your PATH."
)
logger.log(f"Found Elixir: {elixir_version}", logging.INFO)
log.info(f"Found Elixir: {elixir_version}")
platform_id = PlatformUtils.get_platform_id()
@@ -175,9 +174,9 @@ class ElixirTools(SolidLanguageServer):
binary_path = os.path.join(next_ls_dir, dependency.binary_name)
if not os.path.exists(executable_path):
logger.log(f"Downloading Next LS binary from {dependency.url}", logging.INFO)
log.info(f"Downloading Next LS binary from {dependency.url}")
assert dependency.url is not None
FileUtils.download_file(logger, dependency.url, binary_path)
FileUtils.download_file(dependency.url, binary_path)
# Make the binary executable on Unix-like systems
os.chmod(binary_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
@@ -190,17 +189,14 @@ class ElixirTools(SolidLanguageServer):
assert os.path.exists(executable_path), f"Next LS executable not found at {executable_path}"
logger.log(f"Next LS binary ready at: {executable_path}", logging.INFO)
log.info(f"Next LS binary ready at: {executable_path}")
return executable_path
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
nextls_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
nextls_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=f'"{nextls_executable_path}" --stdio', cwd=repository_root_path),
"elixir",
@@ -287,12 +283,12 @@ class ElixirTools(SolidLanguageServer):
def window_log_message(msg: Any) -> None:
"""Handle window/logMessage notifications from Next LS"""
message_text = msg.get("message", "")
self.logger.log(f"LSP: window/logMessage: {message_text}", logging.INFO)
log.info(f"LSP: window/logMessage: {message_text}")
# Check for the specific Next LS readiness signal
# Based on Next LS source: "Runtime for folder #{name} is ready..."
if "Runtime for folder" in message_text and "is ready..." in message_text:
self.logger.log("Next LS runtime is ready based on official log message", logging.INFO)
log.info("Next LS runtime is ready based on official log message")
self.server_ready.set()
def do_nothing(params: Any) -> None:
@@ -310,7 +306,7 @@ class ElixirTools(SolidLanguageServer):
if value.get("kind") == "end":
message = value.get("message", "")
if "has initialized!" in message:
self.logger.log("Next LS initialization progress completed", logging.INFO)
log.info("Next LS initialization progress completed")
# Note: We don't set server_ready here - we wait for the log message
def work_done_progress(params: Any) -> None:
@@ -320,7 +316,7 @@ class ElixirTools(SolidLanguageServer):
"""
value = params.get("value", {})
if value.get("kind") == "end":
self.logger.log("Next LS work done progress completed", logging.INFO)
log.info("Next LS work done progress completed")
# Note: We don't set server_ready here - we wait for the log message
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -330,28 +326,25 @@ class ElixirTools(SolidLanguageServer):
self.server.on_notification("$/workDoneProgress", work_done_progress)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Next LS server process", logging.INFO)
log.info("Starting Next LS server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# Verify server capabilities - be more lenient with Next LS
self.logger.log(f"Next LS capabilities: {list(init_response['capabilities'].keys())}", logging.INFO)
log.info(f"Next LS capabilities: {list(init_response['capabilities'].keys())}")
# Next LS may not provide all capabilities immediately, so we check for basic ones
assert "textDocumentSync" in init_response["capabilities"], f"Missing textDocumentSync in {init_response['capabilities']}"
# Some capabilities might be optional or provided later. This is expected, so we log as info
if "completionProvider" not in init_response["capabilities"]:
self.logger.log("completionProvider not available in initial capabilities", logging.INFO)
log.info("completionProvider not available in initial capabilities")
if "definitionProvider" not in init_response["capabilities"]:
self.logger.log("definitionProvider not available in initial capabilities", logging.INFO)
log.info("definitionProvider not available in initial capabilities")
self.server.notify.initialized({})
self.completions_available.set()
@@ -359,12 +352,12 @@ class ElixirTools(SolidLanguageServer):
# Wait for Next LS to send the specific "Runtime for folder X is ready..." log message
# This is the authoritative signal that Next LS is truly ready for requests
ready_timeout = 180.0
self.logger.log(f"Waiting up to {ready_timeout} seconds for Next LS runtime readiness...", logging.INFO)
log.info(f"Waiting up to {ready_timeout} seconds for Next LS runtime readiness...")
if self.server_ready.wait(timeout=ready_timeout):
self.logger.log("Next LS is ready and available for requests", logging.INFO)
log.info("Next LS is ready and available for requests")
else:
error_msg = f"Next LS failed to initialize within {ready_timeout} seconds. This may indicate a problem with the Elixir installation, project compilation, or Next LS itself."
self.logger.log(error_msg, logging.ERROR)
log.error(error_msg)
raise RuntimeError(error_msg)
@@ -13,33 +13,27 @@ from sensai.util.logging import LogTime
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
class ElmLanguageServer(SolidLanguageServer):
"""
Provides Elm specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Elm.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates an ElmLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
elm_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
elm_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=elm_lsp_executable_path, cwd=repository_root_path),
"elm",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=elm_lsp_executable_path, cwd=repository_root_path), "elm", solidlsp_settings
)
self.server_ready = threading.Event()
@@ -53,16 +47,14 @@ class ElmLanguageServer(SolidLanguageServer):
]
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> list[str]:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> list[str]:
"""
Setup runtime dependencies for Elm Language Server and return the command to start the server.
"""
# Check if elm-language-server is already installed globally
system_elm_ls = shutil.which("elm-language-server")
if system_elm_ls:
logger.log(f"Found system-installed elm-language-server at {system_elm_ls}", logging.INFO)
log.info(f"Found system-installed elm-language-server at {system_elm_ls}")
return [system_elm_ls, "--stdio"]
# Verify node and npm are installed
@@ -86,9 +78,9 @@ class ElmLanguageServer(SolidLanguageServer):
elm_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "elm-lsp")
elm_ls_executable_path = os.path.join(elm_ls_dir, "node_modules", ".bin", "elm-language-server")
if not os.path.exists(elm_ls_executable_path):
logger.log(f"Elm Language Server executable not found at {elm_ls_executable_path}. Installing...", logging.INFO)
with LogTime("Installation of Elm language server dependencies", logger=logger.logger):
deps.install(logger, elm_ls_dir)
log.info(f"Elm Language Server executable not found at {elm_ls_executable_path}. Installing...")
with LogTime("Installation of Elm language server dependencies", logger=log):
deps.install(elm_ls_dir)
if not os.path.exists(elm_ls_executable_path):
raise FileNotFoundError(
@@ -154,20 +146,17 @@ class ElmLanguageServer(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Elm server process", logging.INFO)
log.info("Starting Elm server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# Elm-specific capability checks
@@ -178,11 +167,11 @@ class ElmLanguageServer(SolidLanguageServer):
assert "documentSymbolProvider" in init_response["capabilities"]
self.server.notify.initialized({})
self.logger.log("Elm server initialized successfully, waiting for workspace scan...", logging.INFO)
log.info("Elm server initialized successfully, waiting for workspace scan...")
self.server_ready.set()
self.completions_available.set()
self.logger.log("Elm server ready", logging.INFO)
log.info("Elm server ready")
@override
def _get_wait_time_for_cross_file_referencing(self) -> float:
@@ -11,21 +11,16 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class ErlangLanguageServer(SolidLanguageServer):
"""Language server for Erlang using Erlang LS."""
def __init__(
self,
config: LanguageServerConfig,
logger: LanguageServerLogger,
repository_root_path: str,
solidlsp_settings: SolidLSPSettings,
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates an ErlangLanguageServer instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
@@ -39,7 +34,6 @@ class ErlangLanguageServer(SolidLanguageServer):
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=[self.erlang_ls_path, "--transport", "stdio"], cwd=repository_root_path),
"erlang",
@@ -89,7 +83,7 @@ class ErlangLanguageServer(SolidLanguageServer):
def window_log_message(msg: dict) -> None:
"""Handle window/logMessage notifications from Erlang LS"""
message_text = msg.get("message", "")
self.logger.log(f"LSP: window/logMessage: {message_text}", logging.INFO)
log.info(f"LSP: window/logMessage: {message_text}")
# Look for Erlang LS readiness signals
# Common patterns: "Started Erlang LS", "initialized", "ready"
@@ -105,7 +99,7 @@ class ErlangLanguageServer(SolidLanguageServer):
message_lower = message_text.lower()
for signal in readiness_signals:
if signal.lower() in message_lower:
self.logger.log(f"Erlang LS readiness signal detected: {message_text}", logging.INFO)
log.info(f"Erlang LS readiness signal detected: {message_text}")
self.server_ready.set()
break
@@ -120,7 +114,7 @@ class ErlangLanguageServer(SolidLanguageServer):
if value.get("kind") == "end":
message = value.get("message", "")
if any(word in message.lower() for word in ["initialized", "ready", "complete"]):
self.logger.log("Erlang LS initialization progress completed", logging.INFO)
log.info("Erlang LS initialization progress completed")
# Set as fallback if no window/logMessage was received
if not self.server_ready.is_set():
self.server_ready.set()
@@ -133,7 +127,7 @@ class ErlangLanguageServer(SolidLanguageServer):
self.server.on_notification("$/workDoneProgress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Erlang LS server process", logging.INFO)
log.info("Starting Erlang LS server process")
self.server.start()
# Send initialize request
@@ -153,12 +147,12 @@ class ErlangLanguageServer(SolidLanguageServer):
},
}
self.logger.log("Sending initialize request to Erlang LS", logging.INFO)
log.info("Sending initialize request to Erlang LS")
init_response = self.server.send.initialize(initialize_params) # type: ignore[arg-type]
# Verify server capabilities
if "capabilities" in init_response:
self.logger.log(f"Erlang LS capabilities: {list(init_response['capabilities'].keys())}", logging.INFO)
log.info(f"Erlang LS capabilities: {list(init_response['capabilities'].keys())}")
self.server.notify.initialized({})
self.completions_available.set()
@@ -178,28 +172,26 @@ class ErlangLanguageServer(SolidLanguageServer):
ready_timeout = 60.0 # 1 minute for local
env_desc = "local"
self.logger.log(f"Waiting up to {ready_timeout} seconds for Erlang LS readiness ({env_desc} environment)...", logging.INFO)
log.info(f"Waiting up to {ready_timeout} seconds for Erlang LS readiness ({env_desc} environment)...")
if self.server_ready.wait(timeout=ready_timeout):
self.logger.log("Erlang LS is ready and available for requests", logging.INFO)
log.info("Erlang LS is ready and available for requests")
# Add settling period for indexing - adjust based on environment
settling_time = 15.0 if is_ci else 5.0
self.logger.log(f"Allowing {settling_time} seconds for Erlang LS indexing to complete...", logging.INFO)
log.info(f"Allowing {settling_time} seconds for Erlang LS indexing to complete...")
time.sleep(settling_time)
self.logger.log("Erlang LS settling period complete", logging.INFO)
log.info("Erlang LS settling period complete")
else:
# Set ready anyway and continue - Erlang LS might not send explicit ready messages
self.logger.log(
f"Erlang LS readiness timeout reached after {ready_timeout}s, proceeding anyway (common in CI)", logging.WARNING
)
log.warning(f"Erlang LS readiness timeout reached after {ready_timeout}s, proceeding anyway (common in CI)")
self.server_ready.set()
# Still give some time for basic initialization even without explicit readiness signal
basic_settling_time = 20.0 if is_ci else 10.0
self.logger.log(f"Allowing {basic_settling_time} seconds for basic Erlang LS initialization...", logging.INFO)
log.info(f"Allowing {basic_settling_time} seconds for basic Erlang LS initialization...")
time.sleep(basic_settling_time)
self.logger.log("Basic Erlang LS initialization period complete", logging.INFO)
log.info("Basic Erlang LS initialization period complete")
@override
def is_ignored_dirname(self, dirname: str) -> bool:
@@ -14,11 +14,12 @@ from overrides import override
from solidlsp import ls_types
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class FortranLanguageServer(SolidLanguageServer):
"""Fortran Language Server implementation using fortls."""
@@ -127,10 +128,7 @@ class FortranLanguageServer(SolidLanguageServer):
corrected_symbol = symbol.copy()
corrected_symbol["selectionRange"] = new_sel_range # type: ignore[typeddict-item]
self.logger.log(
f"Fixed fortls selectionRange for {identifier_name}: char {start_char} -> {identifier_start}",
logging.DEBUG,
)
log.debug(f"Fixed fortls selectionRange for {identifier_name}: char {start_char} -> {identifier_start}")
return corrected_symbol
@@ -181,13 +179,7 @@ class FortranLanguageServer(SolidLanguageServer):
raise RuntimeError("fortls is not installed or not in PATH.\nInstall it with: pip install fortls")
return fortls_path
def __init__(
self,
config: LanguageServerConfig,
logger: LanguageServerLogger,
repository_root_path: str,
solidlsp_settings: SolidLSPSettings,
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
# Check fortls installation
fortls_path = self._check_fortls_installation()
@@ -196,12 +188,7 @@ class FortranLanguageServer(SolidLanguageServer):
fortls_cmd = f"{fortls_path}"
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=fortls_cmd, cwd=repository_root_path),
"fortran", # Language ID for LSP
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=fortls_cmd, cwd=repository_root_path), "fortran", solidlsp_settings
)
self.server_ready = threading.Event()
@@ -261,7 +248,7 @@ class FortranLanguageServer(SolidLanguageServer):
"""Start Fortran Language Server process."""
def window_log_message(msg: dict) -> None:
self.logger.log(f"Fortran LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"Fortran LSP: window/logMessage: {msg}")
def do_nothing(params: dict) -> None:
return
@@ -275,14 +262,11 @@ class FortranLanguageServer(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Fortran Language Server (fortls) process", logging.INFO)
log.info("Starting Fortran Language Server (fortls) process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request to Fortran Language Server",
logging.INFO,
)
log.info("Sending initialize request to Fortran Language Server")
init_response = self.server.send.initialize(initialize_params)
@@ -290,17 +274,17 @@ class FortranLanguageServer(SolidLanguageServer):
capabilities = init_response.get("capabilities", {})
assert "textDocumentSync" in capabilities
if "completionProvider" in capabilities:
self.logger.log("Fortran LSP completion provider available", logging.INFO)
log.info("Fortran LSP completion provider available")
if "definitionProvider" in capabilities:
self.logger.log("Fortran LSP definition provider available", logging.INFO)
log.info("Fortran LSP definition provider available")
if "referencesProvider" in capabilities:
self.logger.log("Fortran LSP references provider available", logging.INFO)
log.info("Fortran LSP references provider available")
if "documentSymbolProvider" in capabilities:
self.logger.log("Fortran LSP document symbol provider available", logging.INFO)
log.info("Fortran LSP document symbol provider available")
self.server.notify.initialized({})
self.completions_available.set()
# Fortran Language Server is ready after initialization
self.server_ready.set()
self.logger.log("Fortran Language Server initialization complete", logging.INFO)
log.info("Fortran Language Server initialization complete")
+7 -18
View File
@@ -9,11 +9,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class Gopls(SolidLanguageServer):
"""
@@ -90,19 +91,10 @@ class Gopls(SolidLanguageServer):
return True
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
self._setup_runtime_dependency()
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd="gopls", cwd=repository_root_path),
"go",
solidlsp_settings,
)
super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd="gopls", cwd=repository_root_path), "go", solidlsp_settings)
self.server_ready = threading.Event()
self.request_id = 0
@@ -145,7 +137,7 @@ class Gopls(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params: dict) -> None:
return
@@ -155,14 +147,11 @@ class Gopls(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting gopls server process", logging.INFO)
log.info("Starting gopls server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# Verify server capabilities
@@ -13,11 +13,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class HaskellLanguageServer(SolidLanguageServer):
"""
@@ -71,14 +72,12 @@ class HaskellLanguageServer(SolidLanguageServer):
" - Homebrew (macOS): brew install haskell-language-server"
)
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a HaskellLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
hls_executable_path = self._ensure_hls_installed()
logger.log(f"Using haskell-language-server at: {hls_executable_path}", logging.INFO)
log.info(f"Using haskell-language-server at: {hls_executable_path}")
# Check if there's a haskell subdirectory with Stack/Cabal project
haskell_subdir = os.path.join(repository_root_path, "haskell")
@@ -86,7 +85,7 @@ class HaskellLanguageServer(SolidLanguageServer):
os.path.exists(os.path.join(haskell_subdir, "stack.yaml")) or os.path.exists(os.path.join(haskell_subdir, "cabal.project"))
):
working_dir = haskell_subdir
logger.log(f"Using Haskell project directory: {working_dir}", logging.INFO)
log.info(f"Using Haskell project directory: {working_dir}")
else:
working_dir = repository_root_path
@@ -98,7 +97,6 @@ class HaskellLanguageServer(SolidLanguageServer):
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=[hls_executable_path, "--lsp", "--cwd", working_dir], cwd=working_dir, env=env),
"haskell",
@@ -322,19 +320,19 @@ class HaskellLanguageServer(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def register_capability_handler(params: dict) -> None:
"""Handle dynamic capability registration from HLS"""
if "registrations" in params:
for registration in params.get("registrations", []):
method = registration.get("method", "")
self.logger.log(f"HLS registered capability: {method}", logging.INFO)
log.info(f"HLS registered capability: {method}")
return
def workspace_configuration_handler(params: dict) -> Any:
"""Handle workspace/configuration requests from HLS"""
self.logger.log(f"HLS requesting configuration: {params}", logging.INFO)
log.info(f"HLS requesting configuration: {params}")
# Configuration matching VS Code settings and initialization options
haskell_config = {
@@ -352,7 +350,7 @@ class HaskellLanguageServer(SolidLanguageServer):
result.append(haskell_config)
else:
result.append({})
self.logger.log(f"Returning configuration: {result}", logging.INFO)
log.info(f"Returning configuration: {result}")
return result
# Fallback: return single config
@@ -364,26 +362,23 @@ class HaskellLanguageServer(SolidLanguageServer):
self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_request("workspace/configuration", workspace_configuration_handler)
self.logger.log("Starting Haskell Language Server process", logging.INFO)
log.info("Starting Haskell Language Server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# Log capabilities returned by HLS
capabilities = init_response.get("capabilities", {})
self.logger.log(f"HLS capabilities: {list(capabilities.keys())}", logging.INFO)
log.info(f"HLS capabilities: {list(capabilities.keys())}")
self.server.notify.initialized({})
self.completions_available.set()
# Give HLS time to index the project
# HLS can be slow to index, especially on first run
self.logger.log("Waiting for HLS to index project...", logging.INFO)
log.info("Waiting for HLS to index project...")
time.sleep(5)
self.logger.log("Haskell Language Server initialized successfully", logging.INFO)
log.info("Haskell Language Server initialized successfully")
+12 -27
View File
@@ -12,7 +12,6 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, InitializeParams, LocationLink
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -21,6 +20,8 @@ from solidlsp.settings import SolidLSPSettings
from ..lsp_protocol_handler import lsp_types
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
class Intelephense(SolidLanguageServer):
"""
@@ -37,7 +38,7 @@ class Intelephense(SolidLanguageServer):
return super().is_ignored_dirname(dirname) or dirname in self._ignored_dirnames
@classmethod
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, solidlsp_settings: SolidLSPSettings) -> list[str]:
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> list[str]:
"""
Setup runtime dependencies for Intelephense and return the command to start the server.
"""
@@ -74,7 +75,7 @@ class Intelephense(SolidLanguageServer):
)
]
)
deps.install(logger, intelephense_ls_dir)
deps.install(intelephense_ls_dir)
assert os.path.exists(
intelephense_executable_path
@@ -82,19 +83,12 @@ class Intelephense(SolidLanguageServer):
return [intelephense_executable_path, "--stdio"]
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
# Setup runtime dependencies before initializing
intelephense_cmd = self._setup_runtime_dependencies(logger, solidlsp_settings)
intelephense_cmd = self._setup_runtime_dependencies(solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=intelephense_cmd, cwd=repository_root_path),
"php",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=intelephense_cmd, cwd=repository_root_path), "php", solidlsp_settings
)
self.request_id = 0
@@ -105,10 +99,7 @@ class Intelephense(SolidLanguageServer):
self._ignored_dirnames = {"node_modules", "cache"}
if self._custom_settings.get("ignore_vendor", True):
self._ignored_dirnames.add("vendor")
self.logger.log(
f"Ignoring the following directories for PHP projects: {', '.join(sorted(self._ignored_dirnames))}",
logging.INFO,
)
log.info(f"Ignoring the following directories for PHP projects: {', '.join(sorted(self._ignored_dirnames))}")
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
"""
@@ -157,7 +148,7 @@ class Intelephense(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params: dict) -> None:
return
@@ -167,19 +158,13 @@ class Intelephense(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Intelephense server process", logging.INFO)
log.info("Starting Intelephense server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
self.logger.log(
"After sent initialize params",
logging.INFO,
)
log.info("After sent initialize params")
# Verify server capabilities
assert "textDocumentSync" in init_response["capabilities"]
+6 -11
View File
@@ -11,26 +11,24 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class JediServer(SolidLanguageServer):
"""
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a JediServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd="jedi-language-server", cwd=repository_root_path),
"python",
@@ -169,7 +167,7 @@ class JediServer(SolidLanguageServer):
self.completions_available.set()
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", do_nothing)
self.server.on_notification("language/status", do_nothing)
@@ -180,14 +178,11 @@ class JediServer(SolidLanguageServer):
self.server.on_notification("language/actionableNotification", do_nothing)
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.logger.log("Starting jedi-language-server server process", logging.INFO)
log.info("Starting jedi-language-server server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" in init_response["capabilities"]
+16 -29
View File
@@ -10,25 +10,20 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class JuliaLanguageServer(SolidLanguageServer):
"""
Language server implementation for Julia using LanguageServer.jl.
"""
def __init__(
self,
config: LanguageServerConfig,
logger: LanguageServerLogger,
repository_root_path: str,
solidlsp_settings: SolidLSPSettings,
):
julia_executable = self._setup_runtime_dependency(logger) # PASS LOGGER
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
julia_executable = self._setup_runtime_dependency() # PASS LOGGER
julia_code = "using LanguageServer; runserver()"
julia_ls_cmd: str | list[str]
@@ -47,19 +42,14 @@ class JuliaLanguageServer(SolidLanguageServer):
f"{shlex.quote(repository_root_path)}"
)
logger.log(f"[JULIA DEBUG] Command: {julia_ls_cmd}", logging.INFO)
log.info(f"[JULIA DEBUG] Command: {julia_ls_cmd}")
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=julia_ls_cmd, cwd=repository_root_path),
"julia",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=julia_ls_cmd, cwd=repository_root_path), "julia", solidlsp_settings
)
@staticmethod
def _setup_runtime_dependency(logger: LanguageServerLogger) -> str:
def _setup_runtime_dependency() -> str:
"""
Check if the Julia runtime is available and return its full path.
Raises RuntimeError with a helpful message if the dependency is missing.
@@ -94,17 +84,17 @@ class JuliaLanguageServer(SolidLanguageServer):
result = subprocess.run(check_cmd, check=False, capture_output=True, text=True, timeout=10)
if result.returncode != 0:
# LanguageServer.jl not found, install it
JuliaLanguageServer._install_language_server(julia_path, logger)
JuliaLanguageServer._install_language_server(julia_path)
except subprocess.TimeoutExpired:
# Assume it needs installation
JuliaLanguageServer._install_language_server(julia_path, logger)
JuliaLanguageServer._install_language_server(julia_path)
return julia_path
@staticmethod
def _install_language_server(julia_path: str, logger: LanguageServerLogger) -> None:
def _install_language_server(julia_path: str) -> None:
"""Install LanguageServer.jl package."""
logger.log("LanguageServer.jl not found. Installing... (this may take a minute)", logging.INFO)
log.info("LanguageServer.jl not found. Installing... (this may take a minute)")
install_cmd = [julia_path, "-e", 'using Pkg; Pkg.add("LanguageServer")']
@@ -112,7 +102,7 @@ class JuliaLanguageServer(SolidLanguageServer):
result = subprocess.run(install_cmd, check=False, capture_output=True, text=True, timeout=300) # 5 minutes for installation
if result.returncode == 0:
logger.log("LanguageServer.jl installed successfully!", logging.INFO)
log.info("LanguageServer.jl installed successfully!")
else:
raise RuntimeError(f"Failed to install LanguageServer.jl: {result.stderr}")
except subprocess.TimeoutExpired:
@@ -158,20 +148,17 @@ class JuliaLanguageServer(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting LanguageServer.jl server process", logging.INFO)
log.info("Starting LanguageServer.jl server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request to Julia Language Server",
logging.INFO,
)
log.info("Sending initialize request to Julia Language Server")
init_response = self.server.send.initialize(initialize_params)
assert "definitionProvider" in init_response["capabilities"]
@@ -180,4 +167,4 @@ class JuliaLanguageServer(SolidLanguageServer):
self.server.notify.initialized({})
self.completions_available.set()
self.logger.log("Julia Language Server is initialized and ready.", logging.INFO)
log.info("Julia Language Server is initialized and ready.")
@@ -11,12 +11,13 @@ from typing import cast
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import FileUtils, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
@dataclasses.dataclass
class KotlinRuntimeDependencyPaths:
@@ -34,13 +35,11 @@ class KotlinLanguageServer(SolidLanguageServer):
Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a Kotlin Language Server instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
runtime_dependency_paths = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
runtime_dependency_paths = self._setup_runtime_dependencies(config, solidlsp_settings)
self.runtime_dependency_paths = runtime_dependency_paths
# Create command to execute the Kotlin Language Server script
@@ -50,18 +49,11 @@ class KotlinLanguageServer(SolidLanguageServer):
proc_env = {"JAVA_HOME": self.runtime_dependency_paths.java_home_path}
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=cmd, env=proc_env, cwd=repository_root_path),
"kotlin",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=cmd, env=proc_env, cwd=repository_root_path), "kotlin", solidlsp_settings
)
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> KotlinRuntimeDependencyPaths:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> KotlinRuntimeDependencyPaths:
"""
Setup runtime dependencies for Kotlin Language Server and return the paths.
"""
@@ -130,8 +122,8 @@ class KotlinLanguageServer(SolidLanguageServer):
# Download and extract Java if not exists
if not os.path.exists(java_path):
logger.log(f"Downloading Java for {platform_id.value}...", logging.INFO)
FileUtils.download_and_extract_archive(logger, java_dependency["url"], java_dir, java_dependency["archiveType"])
log.info(f"Downloading Java for {platform_id.value}...")
FileUtils.download_and_extract_archive(java_dependency["url"], java_dir, java_dependency["archiveType"])
# Make Java executable
if not platform_id.value.startswith("win-"):
os.chmod(java_path, 0o755)
@@ -149,8 +141,8 @@ class KotlinLanguageServer(SolidLanguageServer):
# Download and extract Kotlin Language Server if script doesn't exist
if not os.path.exists(kotlin_script):
logger.log("Downloading Kotlin Language Server...", logging.INFO)
FileUtils.download_and_extract_archive(logger, kotlin_dependency["url"], static_dir, kotlin_dependency["archiveType"]) # type: ignore
log.info("Downloading Kotlin Language Server...")
FileUtils.download_and_extract_archive(kotlin_dependency["url"], static_dir, kotlin_dependency["archiveType"]) # type: ignore
# Make script executable on Unix platforms
if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"):
@@ -161,7 +153,7 @@ class KotlinLanguageServer(SolidLanguageServer):
# Use script file
if os.path.exists(kotlin_script):
kotlin_executable_path = kotlin_script
logger.log(f"Using Kotlin Language Server script at {kotlin_script}", logging.INFO)
log.info(f"Using Kotlin Language Server script at {kotlin_script}")
else:
raise FileNotFoundError(f"Kotlin Language Server script not found at {kotlin_script}")
@@ -435,7 +427,7 @@ class KotlinLanguageServer(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", do_nothing)
self.server.on_notification("language/status", do_nothing)
@@ -445,14 +437,11 @@ class KotlinLanguageServer(SolidLanguageServer):
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.server.on_notification("language/actionableNotification", do_nothing)
self.logger.log("Starting Kotlin server process", logging.INFO)
log.info("Starting Kotlin server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
capabilities = init_response["capabilities"]
+7 -16
View File
@@ -17,11 +17,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class LuaLanguageServer(SolidLanguageServer):
"""
@@ -154,18 +155,11 @@ class LuaLanguageServer(SolidLanguageServer):
return lua_ls_path
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
lua_ls_path = self._setup_runtime_dependency()
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=lua_ls_path, cwd=repository_root_path),
"lua",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=lua_ls_path, cwd=repository_root_path), "lua", solidlsp_settings
)
self.server_ready = threading.Event()
self.request_id = 0
@@ -263,7 +257,7 @@ class LuaLanguageServer(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params: dict) -> None:
return
@@ -273,14 +267,11 @@ class LuaLanguageServer(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Lua Language Server process", logging.INFO)
log.info("Starting Lua Language Server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# Verify server capabilities
+12 -20
View File
@@ -12,13 +12,14 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
class Marksman(SolidLanguageServer):
"""
@@ -67,9 +68,7 @@ class Marksman(SolidLanguageServer):
)
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> str:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
"""Setup runtime dependencies for marksman and return the command to start the server."""
deps = cls.runtime_dependencies
dependency = deps.get_single_dep_for_current_platform()
@@ -77,28 +76,24 @@ class Marksman(SolidLanguageServer):
marksman_ls_dir = cls.ls_resources_dir(solidlsp_settings)
marksman_executable_path = deps.binary_path(marksman_ls_dir)
if not os.path.exists(marksman_executable_path):
logger.log(
log.info(
f"Downloading marksman from {dependency.url} to {marksman_ls_dir}",
logging.INFO,
)
deps.install(logger, marksman_ls_dir)
deps.install(marksman_ls_dir)
if not os.path.exists(marksman_executable_path):
raise FileNotFoundError(f"Download failed? Could not find marksman executable at {marksman_executable_path}")
os.chmod(marksman_executable_path, 0o755)
return marksman_executable_path
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a Marksman instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
marksman_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
marksman_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=f"{marksman_executable_path} server", cwd=repository_root_path),
"markdown",
@@ -159,7 +154,7 @@ class Marksman(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(_params: dict) -> None:
return
@@ -169,16 +164,13 @@ class Marksman(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting marksman server process", logging.INFO)
log.info("Starting marksman server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to marksman server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to marksman server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
self.logger.log(f"Received initialize response from marksman server: {init_response}", logging.DEBUG)
log.debug(f"Received initialize response from marksman server: {init_response}")
# Verify server capabilities
assert "textDocumentSync" in init_response["capabilities"]
@@ -188,6 +180,6 @@ class Marksman(SolidLanguageServer):
self.server.notify.initialized({})
# marksman is typically ready immediately after initialization
self.logger.log("Marksman server initialization complete", logging.INFO)
log.info("Marksman server initialization complete")
self.server_ready.set()
self.completions_available.set()
+7 -18
View File
@@ -19,11 +19,12 @@ from overrides import override
from solidlsp import ls_types
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class NixLanguageServer(SolidLanguageServer):
"""
@@ -244,19 +245,10 @@ class NixLanguageServer(SolidLanguageServer):
return nixd_path
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
nixd_path = self._setup_runtime_dependency()
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=nixd_path, cwd=repository_root_path),
"nix",
solidlsp_settings,
)
super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd=nixd_path, cwd=repository_root_path), "nix", solidlsp_settings)
self.server_ready = threading.Event()
self.request_id = 0
@@ -358,7 +350,7 @@ class NixLanguageServer(SolidLanguageServer):
return
def window_log_message(msg):
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params):
return
@@ -368,14 +360,11 @@ class NixLanguageServer(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting nixd server process", logging.INFO)
log.info("Starting nixd server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# Verify server capabilities
+11 -19
View File
@@ -14,12 +14,13 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
def breadth_first_file_scan(root: str) -> Iterable[str]:
"""
@@ -59,17 +60,15 @@ class OmniSharp(SolidLanguageServer):
Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates an OmniSharp instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
omnisharp_executable_path, dll_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
omnisharp_executable_path, dll_path = self._setup_runtime_dependencies(config, solidlsp_settings)
slnfilename = find_least_depth_sln_file(repository_root_path)
if slnfilename is None:
logger.log("No *.sln file found in repository", logging.ERROR)
log.error("No *.sln file found in repository")
raise SolidLSPException("No SLN file found in repository")
cmd = " ".join(
@@ -104,9 +103,7 @@ class OmniSharp(SolidLanguageServer):
"formattingOptions:indentationSize=4",
]
)
super().__init__(
config, logger, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp", solidlsp_settings
)
super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp", solidlsp_settings)
self.server_ready = threading.Event()
self.definition_available = threading.Event()
@@ -142,9 +139,7 @@ class OmniSharp(SolidLanguageServer):
return d
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> tuple[str, str]:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> tuple[str, str]:
"""
Setup runtime dependencies for OmniSharp.
"""
@@ -190,7 +185,7 @@ class OmniSharp(SolidLanguageServer):
omnisharp_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "OmniSharp")
if not os.path.exists(omnisharp_ls_dir):
os.makedirs(omnisharp_ls_dir)
FileUtils.download_and_extract_archive(logger, runtime_dependencies["OmniSharp"]["url"], omnisharp_ls_dir, "zip")
FileUtils.download_and_extract_archive(runtime_dependencies["OmniSharp"]["url"], omnisharp_ls_dir, "zip")
omnisharp_executable_path = os.path.join(omnisharp_ls_dir, runtime_dependencies["OmniSharp"]["binaryName"])
assert os.path.exists(omnisharp_executable_path)
os.chmod(omnisharp_executable_path, 0o755)
@@ -198,7 +193,7 @@ class OmniSharp(SolidLanguageServer):
razor_omnisharp_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "RazorOmnisharp")
if not os.path.exists(razor_omnisharp_ls_dir):
os.makedirs(razor_omnisharp_ls_dir)
FileUtils.download_and_extract_archive(logger, runtime_dependencies["RazorOmnisharp"]["url"], razor_omnisharp_ls_dir, "zip")
FileUtils.download_and_extract_archive(runtime_dependencies["RazorOmnisharp"]["url"], razor_omnisharp_ls_dir, "zip")
razor_omnisharp_dll_path = os.path.join(razor_omnisharp_ls_dir, runtime_dependencies["RazorOmnisharp"]["dll_path"])
assert os.path.exists(razor_omnisharp_dll_path)
@@ -358,14 +353,11 @@ class OmniSharp(SolidLanguageServer):
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.server.on_request("workspace/configuration", workspace_configuration_handler)
self.logger.log("Starting OmniSharp server process", logging.INFO)
log.info("Starting OmniSharp server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
self.server.notify.initialized({})
with open(os.path.join(os.path.dirname(__file__), "omnisharp", "workspace_did_change_configuration.json"), encoding="utf-8") as f:
@@ -15,12 +15,13 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import DidChangeConfigurationParams, InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class PerlLanguageServer(SolidLanguageServer):
"""
@@ -98,19 +99,12 @@ class PerlLanguageServer(SolidLanguageServer):
return "perl -MPerl::LanguageServer -e 'Perl::LanguageServer::run'"
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
# Setup runtime dependencies before initializing
perl_ls_cmd = self._setup_runtime_dependencies()
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=perl_ls_cmd, cwd=repository_root_path),
"perl",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=perl_ls_cmd, cwd=repository_root_path), "perl", solidlsp_settings
)
self.request_id = 0
@@ -156,15 +150,15 @@ class PerlLanguageServer(SolidLanguageServer):
def register_capability_handler(params: Any) -> None:
return
def window_log_message(msg: str) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def window_log_message(msg: dict) -> None:
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params: Any) -> None:
return
def workspace_configuration_handler(params: Any) -> Any:
"""Handle workspace/configuration request from Perl::LanguageServer."""
self.logger.log(f"Received workspace/configuration request: {params}", logging.INFO)
log.info(f"Received workspace/configuration request: {params}")
perl_config = {
"perlInc": [self.repository_root_path, "."],
@@ -180,18 +174,14 @@ class PerlLanguageServer(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Perl::LanguageServer process", logging.INFO)
log.info("Starting Perl::LanguageServer process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
self.logger.log(
log.info(
"After sent initialize params",
logging.INFO,
)
# Verify server capabilities
@@ -213,7 +203,7 @@ class PerlLanguageServer(SolidLanguageServer):
}
}
}
self.logger.log(f"Sending workspace/didChangeConfiguration notification with config: {perl_config}", logging.INFO)
log.info(f"Sending workspace/didChangeConfiguration notification with config: {perl_config}")
self.server.notify.workspace_did_change_configuration(perl_config)
self.completions_available.set()
@@ -221,6 +211,6 @@ class PerlLanguageServer(SolidLanguageServer):
# Perl::LanguageServer needs time to index files and resolve cross-file references
# Without this delay, requests for definitions/references may return empty results
settling_time = 0.5
self.logger.log(f"Allowing {settling_time} seconds for Perl::LanguageServer to index files...", logging.INFO)
log.info(f"Allowing {settling_time} seconds for Perl::LanguageServer to index files...")
time.sleep(settling_time)
self.logger.log("Perl::LanguageServer settling period complete", logging.INFO)
log.info("Perl::LanguageServer settling period complete")
+12 -19
View File
@@ -13,11 +13,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class PyrightServer(SolidLanguageServer):
"""
@@ -25,19 +26,14 @@ class PyrightServer(SolidLanguageServer):
Contains various configurations and settings specific to Python.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a PyrightServer instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
super().__init__(
config,
logger,
repository_root_path,
# Note 1: we can also use `pyright-langserver --stdio` but it requires pyright to be installed with npm
# Note 2: we can also use `bpyright-langserver --stdio` if we ever are unhappy with pyright
ProcessLaunchInfo(cmd="python -m pyright.langserver --stdio", cwd=repository_root_path),
"python",
solidlsp_settings,
@@ -140,12 +136,12 @@ class PyrightServer(SolidLanguageServer):
Pyright logs "Found X source files" when it finishes scanning the workspace.
"""
message_text = msg.get("message", "")
self.logger.log(f"LSP: window/logMessage: {message_text}", logging.INFO)
log.info(f"LSP: window/logMessage: {message_text}")
# Look for "Found X source files" which indicates workspace scanning is complete
# Unfortunately, pyright is unreliable and there seems to be no better way
if re.search(r"Found \d+ source files?", message_text):
self.logger.log("Pyright workspace scanning complete", logging.INFO)
log.info("Pyright workspace scanning complete")
self.found_source_files = True
self.analysis_complete.set()
self.completions_available.set()
@@ -155,7 +151,7 @@ class PyrightServer(SolidLanguageServer):
Also listen for experimental/serverStatus as a backup signal
"""
if params.get("quiescent") == True:
self.logger.log("Received experimental/serverStatus with quiescent=true", logging.INFO)
log.info("Received experimental/serverStatus with quiescent=true")
if not self.found_source_files:
self.analysis_complete.set()
self.completions_available.set()
@@ -170,18 +166,15 @@ class PyrightServer(SolidLanguageServer):
self.server.on_notification("language/actionableNotification", do_nothing)
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.logger.log("Starting pyright-langserver server process", logging.INFO)
log.info("Starting pyright-langserver server process")
self.server.start()
# Send proper initialization parameters
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to pyright server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to pyright server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
self.logger.log(f"Received initialize response from pyright server: {init_response}", logging.INFO)
log.info(f"Received initialize response from pyright server: {init_response}")
# Verify that the server supports our required features
assert "textDocumentSync" in init_response["capabilities"]
@@ -193,11 +186,11 @@ class PyrightServer(SolidLanguageServer):
# Wait for Pyright to complete its initial workspace analysis
# This prevents zombie processes by ensuring background tasks finish
self.logger.log("Waiting for Pyright to complete initial workspace analysis...", logging.INFO)
log.info("Waiting for Pyright to complete initial workspace analysis...")
if self.analysis_complete.wait(timeout=5.0):
self.logger.log("Pyright initial analysis complete, server ready", logging.INFO)
log.info("Pyright initial analysis complete, server ready")
else:
self.logger.log("Timeout waiting for Pyright analysis completion, proceeding anyway", logging.WARNING)
log.warning("Timeout waiting for Pyright analysis completion, proceeding anyway")
# Fallback: assume analysis is complete after timeout
self.analysis_complete.set()
self.completions_available.set()
@@ -9,11 +9,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class RLanguageServer(SolidLanguageServer):
"""R Language Server implementation using the languageserver R package."""
@@ -57,9 +58,7 @@ class RLanguageServer(SolidLanguageServer):
except FileNotFoundError:
raise RuntimeError("R is not installed. Please install R from https://www.r-project.org/")
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
# Check R installation
self._check_r_installation()
@@ -68,14 +67,7 @@ class RLanguageServer(SolidLanguageServer):
# Set specific options to improve parsing stability
r_cmd = 'R --vanilla --quiet --slave -e "options(languageserver.debug_mode = FALSE); languageserver::run()"'
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=r_cmd, cwd=repository_root_path),
"r",
solidlsp_settings,
)
super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd=r_cmd, cwd=repository_root_path), "r", solidlsp_settings)
self.server_ready = threading.Event()
@staticmethod
@@ -132,8 +124,8 @@ class RLanguageServer(SolidLanguageServer):
def _start_server(self) -> None:
"""Start R Language Server process."""
def window_log_message(msg: str) -> None:
self.logger.log(f"R LSP: window/logMessage: {msg}", logging.INFO)
def window_log_message(msg: dict) -> None:
log.info(f"R LSP: window/logMessage: {msg}")
def do_nothing(params: Any) -> None:
return
@@ -147,13 +139,12 @@ class RLanguageServer(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting R Language Server process", logging.INFO)
log.info("Starting R Language Server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
log.info(
"Sending initialize request to R Language Server",
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
@@ -162,9 +153,9 @@ class RLanguageServer(SolidLanguageServer):
capabilities = init_response.get("capabilities", {})
assert "textDocumentSync" in capabilities
if "completionProvider" in capabilities:
self.logger.log("R LSP completion provider available", logging.INFO)
log.info("R LSP completion provider available")
if "definitionProvider" in capabilities:
self.logger.log("R LSP definition provider available", logging.INFO)
log.info("R LSP definition provider available")
self.server.notify.initialized({})
self.completions_available.set()
+6 -10
View File
@@ -9,12 +9,13 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PathUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class RegalLanguageServer(SolidLanguageServer):
"""
@@ -28,16 +29,13 @@ class RegalLanguageServer(SolidLanguageServer):
def is_ignored_dirname(self, dirname: str) -> bool:
return super().is_ignored_dirname(dirname) or dirname in [".regal", ".opa"]
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a RegalLanguageServer instance.
This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
:param config: Language server configuration
:param logger: Logger instance
:param repository_root_path: Path to the repository root
:param solidlsp_settings: Settings for solidlsp
"""
@@ -50,7 +48,6 @@ class RegalLanguageServer(SolidLanguageServer):
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=f"{regal_executable_path} language-server", cwd=repository_root_path),
"rego",
@@ -108,7 +105,7 @@ class RegalLanguageServer(SolidLanguageServer):
return
def window_log_message(msg) -> None: # type: ignore[no-untyped-def]
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params) -> None: # type: ignore[no-untyped-def]
return
@@ -118,13 +115,12 @@ class RegalLanguageServer(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting Regal language server process", logging.INFO)
log.info("Starting Regal language server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
log.info(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
+37 -49
View File
@@ -15,11 +15,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, InitializeResult
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class RubyLsp(SolidLanguageServer):
"""
@@ -27,21 +28,14 @@ class RubyLsp(SolidLanguageServer):
Contains various configurations and settings specific to Ruby with modern LSP features.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a RubyLsp instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
ruby_lsp_executable = self._setup_runtime_dependencies(logger, config, repository_root_path)
ruby_lsp_executable = self._setup_runtime_dependencies(config, repository_root_path)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=ruby_lsp_executable, cwd=repository_root_path),
"ruby",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd=ruby_lsp_executable, cwd=repository_root_path), "ruby", solidlsp_settings
)
self.analysis_complete = threading.Event()
self.service_ready_event = threading.Event()
@@ -98,7 +92,7 @@ class RubyLsp(SolidLanguageServer):
return shutil.which(executable_name)
@staticmethod
def _setup_runtime_dependencies(logger: LanguageServerLogger, config: LanguageServerConfig, repository_root_path: str) -> list[str]:
def _setup_runtime_dependencies(config: LanguageServerConfig, repository_root_path: str) -> list[str]:
"""
Setup runtime dependencies for ruby-lsp and return the command list to start the server.
Installation strategy: Bundler project > global ruby-lsp > gem install ruby-lsp
@@ -125,24 +119,23 @@ class RubyLsp(SolidLanguageServer):
if use_rbenv:
ruby_cmd = ["rbenv", "exec", "ruby"]
bundle_cmd = ["rbenv", "exec", "bundle"]
logger.log(f"Using rbenv-managed Ruby (found {ruby_version_file})", logging.INFO)
log.info(f"Using rbenv-managed Ruby (found {ruby_version_file})")
else:
ruby_cmd = ["ruby"]
bundle_cmd = ["bundle"]
if os.path.exists(ruby_version_file):
logger.log(
log.warning(
f"Found {ruby_version_file} but rbenv is not installed. "
"Using system Ruby. Consider installing rbenv for better version management: https://github.com/rbenv/rbenv",
logging.WARNING,
)
else:
logger.log("No .ruby-version file found, using system Ruby", logging.INFO)
log.info("No .ruby-version file found, using system Ruby")
# Check if Ruby is installed
try:
result = subprocess.run(ruby_cmd + ["--version"], check=True, capture_output=True, cwd=repository_root_path, text=True)
ruby_version = result.stdout.strip()
logger.log(f"Ruby version: {ruby_version}", logging.INFO)
log.info(f"Ruby version: {ruby_version}")
# Extract version number for compatibility checks
import re
@@ -151,7 +144,7 @@ class RubyLsp(SolidLanguageServer):
if version_match:
major, minor, patch = map(int, version_match.groups())
if major < 2 or (major == 2 and minor < 6):
logger.log(f"Warning: Ruby {major}.{minor}.{patch} detected. ruby-lsp works best with Ruby 2.6+", logging.WARNING)
log.warning(f"Warning: Ruby {major}.{minor}.{patch} detected. ruby-lsp works best with Ruby 2.6+")
except subprocess.CalledProcessError as e:
error_msg = e.stderr if isinstance(e.stderr, str) else e.stderr.decode() if e.stderr else "Unknown error"
@@ -173,7 +166,7 @@ class RubyLsp(SolidLanguageServer):
is_bundler_project = os.path.exists(gemfile_path)
if is_bundler_project:
logger.log("Detected Bundler project (Gemfile found)", logging.INFO)
log.info("Detected Bundler project (Gemfile found)")
# Check if bundle command is available using Windows-compatible search
bundle_path = RubyLsp._find_executable_with_extensions(bundle_cmd[0] if len(bundle_cmd) == 1 else "bundle")
@@ -190,9 +183,8 @@ class RubyLsp(SolidLanguageServer):
break
if not bundle_path:
logger.log(
log.warning(
"Bundler project detected but 'bundle' command not found. Falling back to global ruby-lsp installation.",
logging.WARNING,
)
else:
# Check if ruby-lsp is in Gemfile.lock
@@ -203,29 +195,28 @@ class RubyLsp(SolidLanguageServer):
content = f.read()
ruby_lsp_in_bundle = "ruby-lsp" in content.lower()
except Exception as e:
logger.log(f"Warning: Could not read Gemfile.lock: {e}", logging.WARNING)
log.warning(f"Warning: Could not read Gemfile.lock: {e}")
if ruby_lsp_in_bundle:
logger.log("Found ruby-lsp in Gemfile.lock", logging.INFO)
log.info("Found ruby-lsp in Gemfile.lock")
return bundle_cmd + ["exec", "ruby-lsp"]
else:
logger.log(
log.info(
"ruby-lsp not found in Gemfile.lock. Consider adding 'gem \"ruby-lsp\"' to your Gemfile for better compatibility.",
logging.INFO,
)
# Fall through to global installation check
# Check if ruby-lsp is available globally using Windows-compatible search
ruby_lsp_path = RubyLsp._find_executable_with_extensions("ruby-lsp")
if ruby_lsp_path:
logger.log(f"Found ruby-lsp at: {ruby_lsp_path}", logging.INFO)
log.info(f"Found ruby-lsp at: {ruby_lsp_path}")
return [ruby_lsp_path]
# Try to install ruby-lsp globally
logger.log("ruby-lsp not found, attempting to install globally...", logging.INFO)
log.info("ruby-lsp not found, attempting to install globally...")
try:
subprocess.run(["gem", "install", "ruby-lsp"], check=True, capture_output=True, cwd=repository_root_path)
logger.log("Successfully installed ruby-lsp globally", logging.INFO)
log.info("Successfully installed ruby-lsp globally")
# Find the newly installed ruby-lsp executable
ruby_lsp_path = RubyLsp._find_executable_with_extensions("ruby-lsp")
return [ruby_lsp_path] if ruby_lsp_path else ["ruby-lsp"]
@@ -351,13 +342,13 @@ class RubyLsp(SolidLanguageServer):
def register_capability_handler(params: dict) -> None:
assert "registrations" in params
for registration in params["registrations"]:
self.logger.log(f"Registered capability: {registration['method']}", logging.INFO)
log.info(f"Registered capability: {registration['method']}")
return
def lang_status_handler(params: dict) -> None:
self.logger.log(f"LSP: language/status: {params}", logging.INFO)
log.info(f"LSP: language/status: {params}")
if params.get("type") == "ready":
self.logger.log("ruby-lsp service is ready.", logging.INFO)
log.info("ruby-lsp service is ready.")
self.analysis_complete.set()
self.completions_available.set()
@@ -368,36 +359,36 @@ class RubyLsp(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def progress_handler(params: dict) -> None:
# ruby-lsp sends progress notifications during indexing
self.logger.log(f"LSP: $/progress: {params}", logging.DEBUG)
log.debug(f"LSP: $/progress: {params}")
if "value" in params:
value = params["value"]
# Check for completion indicators
if value.get("kind") == "end":
self.logger.log("ruby-lsp indexing complete ($/progress end)", logging.INFO)
log.info("ruby-lsp indexing complete ($/progress end)")
self.analysis_complete.set()
self.completions_available.set()
elif value.get("kind") == "begin":
self.logger.log("ruby-lsp indexing started ($/progress begin)", logging.INFO)
log.info("ruby-lsp indexing started ($/progress begin)")
elif "percentage" in value:
percentage = value.get("percentage", 0)
self.logger.log(f"ruby-lsp indexing progress: {percentage}%", logging.DEBUG)
log.debug(f"ruby-lsp indexing progress: {percentage}%")
# Handle direct progress format (fallback)
elif "token" in params and "value" in params:
token = params.get("token")
if isinstance(token, str) and "indexing" in token.lower():
value = params.get("value", {})
if value.get("kind") == "end" or value.get("percentage") == 100:
self.logger.log("ruby-lsp indexing complete (token progress)", logging.INFO)
log.info("ruby-lsp indexing complete (token progress)")
self.analysis_complete.set()
self.completions_available.set()
def window_work_done_progress_create(params: dict) -> dict:
"""Handle workDoneProgress/create requests from ruby-lsp"""
self.logger.log(f"LSP: window/workDoneProgress/create: {params}", logging.DEBUG)
log.debug(f"LSP: window/workDoneProgress/create: {params}")
return {}
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -408,17 +399,14 @@ class RubyLsp(SolidLanguageServer):
self.server.on_request("window/workDoneProgress/create", window_work_done_progress_create)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting ruby-lsp server process", logging.INFO)
log.info("Starting ruby-lsp server process")
self.server.start()
initialize_params = self._get_initialize_params()
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
self.logger.log(f"Sending init params: {json.dumps(initialize_params, indent=4)}", logging.INFO)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
log.info(f"Sending init params: {json.dumps(initialize_params, indent=4)}")
init_response = self.server.send.initialize(initialize_params)
self.logger.log(f"Received init response: {init_response}", logging.INFO)
log.info(f"Received init response: {init_response}")
# Verify expected capabilities
# Note: ruby-lsp may return textDocumentSync in different formats (number or object)
@@ -434,11 +422,11 @@ class RubyLsp(SolidLanguageServer):
self.server.notify.initialized({})
# Wait for ruby-lsp to complete its initial indexing
# ruby-lsp has fast indexing
self.logger.log("Waiting for ruby-lsp to complete initial indexing...", logging.INFO)
log.info("Waiting for ruby-lsp to complete initial indexing...")
if self.analysis_complete.wait(timeout=30.0):
self.logger.log("ruby-lsp initial indexing complete, server ready", logging.INFO)
log.info("ruby-lsp initial indexing complete, server ready")
else:
self.logger.log("Timeout waiting for ruby-lsp indexing completion, proceeding anyway", logging.WARNING)
log.warning("Timeout waiting for ruby-lsp indexing completion, proceeding anyway")
# Fallback: assume indexing is complete after timeout
self.analysis_complete.set()
self.completions_available.set()
@@ -472,7 +460,7 @@ class RubyLsp(SolidLanguageServer):
for cap in important_capabilities:
if cap in capabilities:
self.logger.log(f"ruby-lsp {cap}: available", logging.DEBUG)
log.debug(f"ruby-lsp {cap}: available")
# Signal that the service is ready
self.service_ready_event.set()
+7 -12
View File
@@ -13,11 +13,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class RustAnalyzer(SolidLanguageServer):
"""
@@ -95,18 +96,15 @@ class RustAnalyzer(SolidLanguageServer):
return path
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
rustanalyzer_executable_path = self._ensure_rust_analyzer_installed()
logger.log(f"Using rust-analyzer at: {rustanalyzer_executable_path}", logging.INFO)
log.info(f"Using rust-analyzer at: {rustanalyzer_executable_path}")
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=rustanalyzer_executable_path, cwd=repository_root_path),
"rust",
@@ -626,7 +624,7 @@ class RustAnalyzer(SolidLanguageServer):
self.server_ready.set()
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_notification("language/status", lang_status_handler)
@@ -637,14 +635,11 @@ class RustAnalyzer(SolidLanguageServer):
self.server.on_notification("language/actionableNotification", do_nothing)
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.logger.log("Starting RustAnalyzer server process", logging.INFO)
log.info("Starting RustAnalyzer server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" in init_response["capabilities"]
@@ -12,7 +12,6 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -22,21 +21,21 @@ if not PlatformUtils.get_platform_id().value.startswith("win"):
pass
log = logging.getLogger(__name__)
class ScalaLanguageServer(SolidLanguageServer):
"""
Provides Scala specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Scala.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a ScalaLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
scala_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
scala_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=scala_lsp_executable_path, cwd=repository_root_path),
config.code_language.value,
@@ -52,9 +51,7 @@ class ScalaLanguageServer(SolidLanguageServer):
]
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> list[str]:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> list[str]:
"""
Setup runtime dependencies for Scala Language Server and return the command to start the server.
"""
@@ -69,9 +66,9 @@ class ScalaLanguageServer(SolidLanguageServer):
if not os.path.exists(metals_executable):
if not cs_command_path:
logger.log("'cs' command not found. Trying to install it using 'coursier'.", logging.INFO)
log.info("'cs' command not found. Trying to install it using 'coursier'.")
try:
logger.log("Running 'coursier setup --yes' to install 'cs'...", logging.INFO)
log.info("Running 'coursier setup --yes' to install 'cs'...")
subprocess.run([coursier_command_path, "setup", "--yes"], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to set up 'cs' command with 'coursier setup'. Stderr: {e.stderr}")
@@ -81,9 +78,9 @@ class ScalaLanguageServer(SolidLanguageServer):
raise RuntimeError(
"'cs' command not found after running 'coursier setup'. Please check your PATH or install it manually."
)
logger.log("'cs' command installed successfully.", logging.INFO)
log.info("'cs' command installed successfully.")
logger.log(f"metals executable not found at {metals_executable}, bootstrapping...", logging.INFO)
log.info(f"metals executable not found at {metals_executable}, bootstrapping...")
artifact = "org.scalameta:metals_2.13:1.6.2"
cmd = [
cs_command_path,
@@ -103,9 +100,9 @@ class ScalaLanguageServer(SolidLanguageServer):
metals_executable,
"-f",
]
logger.log("Bootstrapping metals...", logging.INFO)
log.info("Bootstrapping metals...")
subprocess.run(cmd, cwd=metals_home, check=True)
logger.log("Bootstrapping metals finished.", logging.INFO)
log.info("Bootstrapping metals finished.")
return [metals_executable]
@staticmethod
@@ -159,13 +156,10 @@ class ScalaLanguageServer(SolidLanguageServer):
"""
Starts the Scala Language Server
"""
self.logger.log("Starting Scala server process", logging.INFO)
log.info("Starting Scala server process")
self.server.start()
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
initialize_params = self._get_initialize_params(self.repository_root_path)
self.server.send.initialize(initialize_params)
+23 -29
View File
@@ -16,11 +16,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class Solargraph(SolidLanguageServer):
"""
@@ -28,17 +29,14 @@ class Solargraph(SolidLanguageServer):
Contains various configurations and settings specific to Ruby.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a Solargraph instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
solargraph_executable_path = self._setup_runtime_dependencies(logger, config, repository_root_path)
solargraph_executable_path = self._setup_runtime_dependencies(config, repository_root_path)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=f"{solargraph_executable_path} stdio", cwd=repository_root_path),
"ruby",
@@ -73,7 +71,7 @@ class Solargraph(SolidLanguageServer):
return super().is_ignored_dirname(dirname) or dirname in ruby_ignored_dirs
@staticmethod
def _setup_runtime_dependencies(logger: LanguageServerLogger, config: LanguageServerConfig, repository_root_path: str) -> str:
def _setup_runtime_dependencies(config: LanguageServerConfig, repository_root_path: str) -> str:
"""
Setup runtime dependencies for Solargraph and return the command to start the server.
"""
@@ -81,14 +79,14 @@ class Solargraph(SolidLanguageServer):
try:
result = subprocess.run(["ruby", "--version"], check=True, capture_output=True, cwd=repository_root_path, text=True)
ruby_version = result.stdout.strip()
logger.log(f"Ruby version: {ruby_version}", logging.INFO)
log.info(f"Ruby version: {ruby_version}")
# Extract version number for compatibility checks
version_match = re.search(r"ruby (\d+)\.(\d+)\.(\d+)", ruby_version)
if version_match:
major, minor, patch = map(int, version_match.groups())
if major < 2 or (major == 2 and minor < 6):
logger.log(f"Warning: Ruby {major}.{minor}.{patch} detected. Solargraph works best with Ruby 2.6+", logging.WARNING)
log.warning(f"Warning: Ruby {major}.{minor}.{patch} detected. Solargraph works best with Ruby 2.6+")
except subprocess.CalledProcessError as e:
error_msg = e.stderr.decode() if e.stderr else "Unknown error"
@@ -124,7 +122,7 @@ class Solargraph(SolidLanguageServer):
is_bundler_project = os.path.exists(gemfile_path)
if is_bundler_project:
logger.log("Detected Bundler project (Gemfile found)", logging.INFO)
log.info("Detected Bundler project (Gemfile found)")
# Check if bundle command is available
bundle_path = find_executable_with_extensions("bundle")
@@ -155,15 +153,14 @@ class Solargraph(SolidLanguageServer):
content = f.read()
solargraph_in_bundle = "solargraph" in content.lower()
except Exception as e:
logger.log(f"Warning: Could not read Gemfile.lock: {e}", logging.WARNING)
log.warning(f"Warning: Could not read Gemfile.lock: {e}")
if solargraph_in_bundle:
logger.log("Found solargraph in Gemfile.lock", logging.INFO)
log.info("Found solargraph in Gemfile.lock")
return f"{bundle_path} exec solargraph"
else:
logger.log(
log.warning(
"solargraph not found in Gemfile.lock. Please add 'gem \"solargraph\"' to your Gemfile and run 'bundle install'",
logging.WARNING,
)
# Fall through to global installation check
@@ -171,7 +168,7 @@ class Solargraph(SolidLanguageServer):
# First, try to find solargraph in PATH (includes asdf shims) with Windows support
solargraph_path = find_executable_with_extensions("solargraph")
if solargraph_path:
logger.log(f"Found solargraph at: {solargraph_path}", logging.INFO)
log.info(f"Found solargraph at: {solargraph_path}")
return solargraph_path
# Fallback to gem exec (for non-Bundler projects or when global solargraph not found)
@@ -191,7 +188,7 @@ class Solargraph(SolidLanguageServer):
["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path
)
if result.stdout.strip() == "false":
logger.log("Installing Solargraph...", logging.INFO)
log.info("Installing Solargraph...")
subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path)
return "gem exec solargraph"
@@ -319,9 +316,9 @@ class Solargraph(SolidLanguageServer):
return
def lang_status_handler(params: dict) -> None:
self.logger.log(f"LSP: language/status: {params}", logging.INFO)
log.info(f"LSP: language/status: {params}")
if params.get("type") == "ServiceReady" and params.get("message") == "Service is ready.":
self.logger.log("Solargraph service is ready.", logging.INFO)
log.info("Solargraph service is ready.")
self.analysis_complete.set()
self.completions_available.set()
@@ -332,7 +329,7 @@ class Solargraph(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_notification("language/status", lang_status_handler)
@@ -342,17 +339,14 @@ class Solargraph(SolidLanguageServer):
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.server.on_notification("language/actionableNotification", do_nothing)
self.logger.log("Starting solargraph server process", logging.INFO)
log.info("Starting solargraph server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
self.logger.log(f"Sending init params: {json.dumps(initialize_params, indent=4)}", logging.INFO)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
log.info(f"Sending init params: {json.dumps(initialize_params, indent=4)}")
init_response = self.server.send.initialize(initialize_params)
self.logger.log(f"Received init response: {init_response}", logging.INFO)
log.info(f"Received init response: {init_response}")
assert init_response["capabilities"]["textDocumentSync"] == 2
assert "completionProvider" in init_response["capabilities"]
assert init_response["capabilities"]["completionProvider"] == {
@@ -363,11 +357,11 @@ class Solargraph(SolidLanguageServer):
# Wait for Solargraph to complete its initial workspace analysis
# This prevents issues by ensuring background tasks finish
self.logger.log("Waiting for Solargraph to complete initial workspace analysis...", logging.INFO)
log.info("Waiting for Solargraph to complete initial workspace analysis...")
if self.analysis_complete.wait(timeout=60.0):
self.logger.log("Solargraph initial analysis complete, server ready", logging.INFO)
log.info("Solargraph initial analysis complete, server ready")
else:
self.logger.log("Timeout waiting for Solargraph analysis completion, proceeding anyway", logging.WARNING)
log.warning("Timeout waiting for Solargraph analysis completion, proceeding anyway")
# Fallback: assume analysis is complete after timeout
self.analysis_complete.set()
self.completions_available.set()
+11 -23
View File
@@ -10,11 +10,12 @@ from overrides import override
from solidlsp import ls_types
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class SourceKitLSP(SolidLanguageServer):
"""
@@ -45,19 +46,12 @@ class SourceKitLSP(SolidLanguageServer):
"And make sure it is available on your PATH."
) from e
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
sourcekit_version = self._get_sourcekit_lsp_version()
logger.log(f"Starting sourcekit lsp with version: {sourcekit_version}", logging.INFO)
log.info(f"Starting sourcekit lsp with version: {sourcekit_version}")
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd="sourcekit-lsp", cwd=repository_root_path),
"swift",
solidlsp_settings,
config, repository_root_path, ProcessLaunchInfo(cmd="sourcekit-lsp", cwd=repository_root_path), "swift", solidlsp_settings
)
self.server_ready = threading.Event()
self.request_id = 0
@@ -314,7 +308,7 @@ class SourceKitLSP(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(_params: dict) -> None:
return
@@ -324,18 +318,15 @@ class SourceKitLSP(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting sourcekit-lsp server process", logging.INFO)
log.info("Starting sourcekit-lsp server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
capabilities = init_response["capabilities"]
self.logger.log(f"SourceKit LSP capabilities: {list(capabilities.keys())}", logging.INFO)
log.info(f"SourceKit LSP capabilities: {list(capabilities.keys())}")
assert "textDocumentSync" in capabilities, "textDocumentSync capability missing"
assert "definitionProvider" in capabilities, "definitionProvider capability missing"
@@ -366,10 +357,7 @@ class SourceKitLSP(SolidLanguageServer):
# Fallback if initialization timestamp is missing
remaining_delay = 15 if os.getenv("CI") else 5
self.logger.log(
f"Sleeping {remaining_delay:.1f}s before requesting references for the first time (CI needs extra indexing time)",
logging.INFO,
)
log.info(f"Sleeping {remaining_delay:.1f}s before requesting references for the first time (CI needs extra indexing time)")
time.sleep(remaining_delay)
self._did_sleep_before_requesting_references = True
@@ -378,7 +366,7 @@ class SourceKitLSP(SolidLanguageServer):
# In CI, if no references found, retry once after additional delay
if os.getenv("CI") and not references:
self.logger.log("No references found in CI - retrying after additional 5s delay", logging.INFO)
log.info("No references found in CI - retrying after additional 5s delay")
time.sleep(5)
references = super().request_references(relative_file_path, line, column)
+16 -21
View File
@@ -8,7 +8,6 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PathUtils, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -16,6 +15,8 @@ from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
class TerraformLS(SolidLanguageServer):
"""
@@ -55,13 +56,13 @@ class TerraformLS(SolidLanguageServer):
return SolidLanguageServer._determine_log_level(line)
@staticmethod
def _ensure_tf_command_available(logger: LanguageServerLogger) -> None:
logger.log("Starting terraform version detection...", logging.DEBUG)
def _ensure_tf_command_available() -> None:
log.debug("Starting terraform version detection...")
# 1. Try to find terraform using shutil.which
terraform_cmd = shutil.which("terraform")
if terraform_cmd is not None:
logger.log(f"Found terraform via shutil.which: {terraform_cmd}", logging.DEBUG)
log.debug(f"Found terraform via shutil.which: {terraform_cmd}")
return
# TODO: is this needed?
@@ -69,7 +70,7 @@ class TerraformLS(SolidLanguageServer):
if not terraform_cmd:
terraform_cli_path = os.environ.get("TERRAFORM_CLI_PATH")
if terraform_cli_path:
logger.log(f"Trying TERRAFORM_CLI_PATH: {terraform_cli_path}", logging.DEBUG)
log.debug(f"Trying TERRAFORM_CLI_PATH: {terraform_cli_path}")
# TODO: use binary name from runtime dependencies if we keep this code
if os.name == "nt":
terraform_binary = os.path.join(terraform_cli_path, "terraform.exe")
@@ -77,7 +78,7 @@ class TerraformLS(SolidLanguageServer):
terraform_binary = os.path.join(terraform_cli_path, "terraform")
if os.path.exists(terraform_binary):
terraform_cmd = terraform_binary
logger.log(f"Found terraform via TERRAFORM_CLI_PATH: {terraform_cmd}", logging.DEBUG)
log.debug(f"Found terraform via TERRAFORM_CLI_PATH: {terraform_cmd}")
return
raise RuntimeError(
@@ -86,12 +87,12 @@ class TerraformLS(SolidLanguageServer):
)
@classmethod
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, solidlsp_settings: SolidLSPSettings) -> str:
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
"""
Setup runtime dependencies for terraform-ls.
Downloads and installs terraform-ls if not already present.
"""
cls._ensure_tf_command_available(logger)
cls._ensure_tf_command_available()
platform_id = PlatformUtils.get_platform_id()
deps = RuntimeDependencyCollection(
[
@@ -141,8 +142,8 @@ class TerraformLS(SolidLanguageServer):
terraform_ls_executable_path = deps.binary_path(cls.ls_resources_dir(solidlsp_settings))
if not os.path.exists(terraform_ls_executable_path):
logger.log(f"Downloading terraform-ls from {dependency.url}", logging.INFO)
deps.install(logger, cls.ls_resources_dir(solidlsp_settings))
log.info(f"Downloading terraform-ls from {dependency.url}")
deps.install(cls.ls_resources_dir(solidlsp_settings))
assert os.path.exists(terraform_ls_executable_path), f"terraform-ls executable not found at {terraform_ls_executable_path}"
@@ -152,17 +153,14 @@ class TerraformLS(SolidLanguageServer):
return terraform_ls_executable_path
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a TerraformLS instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
terraform_ls_executable_path = self._setup_runtime_dependencies(logger, solidlsp_settings)
terraform_ls_executable_path = self._setup_runtime_dependencies(solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=f"{terraform_ls_executable_path} serve", cwd=repository_root_path),
"terraform",
@@ -211,7 +209,7 @@ class TerraformLS(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params: dict) -> None:
return
@@ -221,14 +219,11 @@ class TerraformLS(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting terraform-ls server process", logging.INFO)
log.info("Starting terraform-ls server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# Verify server capabilities
@@ -14,7 +14,6 @@ from sensai.util.logging import LogTime
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -22,6 +21,8 @@ from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
# Platform-specific imports
if os.name != "nt": # Unix-like systems
import pwd
@@ -43,16 +44,13 @@ class TypeScriptLanguageServer(SolidLanguageServer):
Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
ts_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
ts_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=ts_lsp_executable_path, cwd=repository_root_path),
"typescript",
@@ -76,9 +74,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
return SolidLanguageServer._determine_log_level(line)
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> list[str]:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> list[str]:
"""
Setup runtime dependencies for TypeScript Language Server and return the command to start the server.
"""
@@ -128,9 +124,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
tsserver_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "ts-lsp")
tsserver_executable_path = os.path.join(tsserver_ls_dir, "node_modules", ".bin", "typescript-language-server")
if not os.path.exists(tsserver_executable_path):
logger.log(f"Typescript Language Server executable not found at {tsserver_executable_path}. Installing...", logging.INFO)
with LogTime("Installation of TypeScript language server dependencies", logger=logger.logger):
deps.install(logger, tsserver_ls_dir)
log.info(f"Typescript Language Server executable not found at {tsserver_executable_path}. Installing...")
with LogTime("Installation of TypeScript language server dependencies", logger=log):
deps.install(tsserver_ls_dir)
if not os.path.exists(tsserver_executable_path):
raise FileNotFoundError(
@@ -210,7 +206,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def check_experimental_status(params: dict) -> None:
"""
@@ -227,13 +223,12 @@ class TypeScriptLanguageServer(SolidLanguageServer):
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.logger.log("Starting TypeScript server process", logging.INFO)
log.info("Starting TypeScript server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
log.info(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
@@ -247,9 +242,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
self.server.notify.initialized({})
if self.server_ready.wait(timeout=1.0):
self.logger.log("TypeScript server is ready", logging.INFO)
log.info("TypeScript server is ready")
else:
self.logger.log("Timeout waiting for TypeScript server to become ready, proceeding anyway", logging.INFO)
log.info("Timeout waiting for TypeScript server to become ready, proceeding anyway")
# Fallback: assume server is ready after timeout
self.server_ready.set()
self.completions_available.set()
@@ -15,7 +15,6 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -23,6 +22,8 @@ from solidlsp.settings import SolidLSPSettings
from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__)
class VtsLanguageServer(SolidLanguageServer):
"""
@@ -30,16 +31,13 @@ class VtsLanguageServer(SolidLanguageServer):
Contains various configurations and settings specific to TypeScript via vtsls wrapper.
"""
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a VtsLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
vts_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
vts_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=vts_lsp_executable_path, cwd=repository_root_path),
"typescript",
@@ -58,9 +56,7 @@ class VtsLanguageServer(SolidLanguageServer):
]
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> str:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
"""
Setup runtime dependencies for VTS Language Server and return the command to start the server.
"""
@@ -99,7 +95,7 @@ class VtsLanguageServer(SolidLanguageServer):
# Install vtsls if not already installed
if not os.path.exists(vts_ls_dir):
os.makedirs(vts_ls_dir, exist_ok=True)
deps.install(logger, vts_ls_dir)
deps.install(vts_ls_dir)
vts_executable_path = os.path.join(vts_ls_dir, "node_modules", ".bin", "vtsls")
@@ -182,7 +178,7 @@ class VtsLanguageServer(SolidLanguageServer):
return
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
log.info(f"LSP: window/logMessage: {msg}")
def check_experimental_status(params: dict) -> None:
"""
@@ -200,33 +196,30 @@ class VtsLanguageServer(SolidLanguageServer):
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.server.on_notification("experimental/serverStatus", check_experimental_status)
self.logger.log("Starting VTS server process", logging.INFO)
log.info("Starting VTS server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# VTS-specific capability checks
# Be more flexible with capabilities since vtsls might have different structure
self.logger.log(f"VTS init response capabilities: {init_response['capabilities']}", logging.DEBUG)
log.debug(f"VTS init response capabilities: {init_response['capabilities']}")
# Basic checks to ensure essential capabilities are present
assert "textDocumentSync" in init_response["capabilities"]
assert "completionProvider" in init_response["capabilities"]
# Log the actual values for debugging
self.logger.log(f"textDocumentSync: {init_response['capabilities']['textDocumentSync']}", logging.DEBUG)
self.logger.log(f"completionProvider: {init_response['capabilities']['completionProvider']}", logging.DEBUG)
log.debug(f"textDocumentSync: {init_response['capabilities']['textDocumentSync']}")
log.debug(f"completionProvider: {init_response['capabilities']['completionProvider']}")
self.server.notify.initialized({})
if self.server_ready.wait(timeout=1.0):
self.logger.log("VTS server is ready", logging.INFO)
log.info("VTS server is ready")
else:
self.logger.log("Timeout waiting for VTS server to become ready, proceeding anyway", logging.INFO)
log.info("Timeout waiting for VTS server to become ready, proceeding anyway")
# Fallback: assume server is ready after timeout
self.server_ready.set()
self.completions_available.set()
@@ -13,11 +13,12 @@ from typing import Any
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class YamlLanguageServer(SolidLanguageServer):
"""
@@ -41,17 +42,14 @@ class YamlLanguageServer(SolidLanguageServer):
return SolidLanguageServer._determine_log_level(line)
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a YamlLanguageServer instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
yaml_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
yaml_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=yaml_lsp_executable_path, cwd=repository_root_path),
"yaml",
@@ -60,9 +58,7 @@ class YamlLanguageServer(SolidLanguageServer):
self.server_ready = threading.Event()
@classmethod
def _setup_runtime_dependencies(
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
) -> str:
def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
"""
Setup runtime dependencies for YAML Language Server and return the command to start the server.
"""
@@ -92,9 +88,9 @@ class YamlLanguageServer(SolidLanguageServer):
yaml_executable_path += ".cmd"
if not os.path.exists(yaml_executable_path):
logger.log(f"YAML Language Server executable not found at {yaml_executable_path}. Installing...", logging.INFO)
deps.install(logger, yaml_ls_dir)
logger.log("YAML language server dependencies installed successfully", logging.INFO)
log.info(f"YAML Language Server executable not found at {yaml_executable_path}. Installing...")
deps.install(yaml_ls_dir)
log.info("YAML language server dependencies installed successfully")
if not os.path.exists(yaml_executable_path):
raise FileNotFoundError(
@@ -162,34 +158,31 @@ class YamlLanguageServer(SolidLanguageServer):
def do_nothing(params: Any) -> None:
return
def window_log_message(msg: str) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def window_log_message(msg: dict) -> None:
log.info(f"LSP: window/logMessage: {msg}")
self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting YAML server process", logging.INFO)
log.info("Starting YAML server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
self.logger.log(f"Received initialize response from YAML server: {init_response}", logging.DEBUG)
log.debug(f"Received initialize response from YAML server: {init_response}")
# Verify document symbol support is available
if "documentSymbolProvider" in init_response["capabilities"]:
self.logger.log("YAML server supports document symbols", logging.INFO)
log.info("YAML server supports document symbols")
else:
self.logger.log("Warning: YAML server does not report document symbol support", logging.WARNING)
log.warning("Warning: YAML server does not report document symbol support")
self.server.notify.initialized({})
# YAML language server is ready immediately after initialization
self.logger.log("YAML server initialization complete", logging.INFO)
log.info("YAML server initialization complete")
self.server_ready.set()
self.completions_available.set()
+10 -21
View File
@@ -14,11 +14,12 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
class ZigLanguageServer(SolidLanguageServer):
"""
@@ -95,19 +96,10 @@ class ZigLanguageServer(SolidLanguageServer):
return True
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
self._setup_runtime_dependency()
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd="zls", cwd=repository_root_path),
"zig",
solidlsp_settings,
)
super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd="zls", cwd=repository_root_path), "zig", solidlsp_settings)
self.server_ready = threading.Event()
self.request_id = 0
@@ -196,8 +188,8 @@ class ZigLanguageServer(SolidLanguageServer):
def register_capability_handler(params: dict) -> None:
return
def window_log_message(msg: str) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def window_log_message(msg: dict) -> None:
log.info(f"LSP: window/logMessage: {msg}")
def do_nothing(params: dict) -> None:
return
@@ -207,14 +199,11 @@ class ZigLanguageServer(SolidLanguageServer):
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting ZLS server process", logging.INFO)
log.info("Starting ZLS server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
# Verify server capabilities
@@ -247,6 +236,6 @@ class ZigLanguageServer(SolidLanguageServer):
}
}
)
self.logger.log("Opened build.zig to provide project context to ZLS", logging.INFO)
log.info("Opened build.zig to provide project context to ZLS")
except Exception as e:
self.logger.log(f"Failed to open build.zig: {e}", logging.WARNING)
log.warning(f"Failed to open build.zig: {e}")
+44 -95
View File
@@ -25,7 +25,6 @@ from solidlsp import ls_types
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_handler import SolidLanguageServerHandler
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_types import UnifiedSymbolInformation
from solidlsp.ls_utils import FileUtils, PathUtils, TextUtils
from solidlsp.lsp_protocol_handler import lsp_types
@@ -207,7 +206,6 @@ class SolidLanguageServer(ABC):
def create(
cls,
config: LanguageServerConfig,
logger: LanguageServerLogger,
repository_root_path: str,
timeout: float | None = None,
solidlsp_settings: SolidLSPSettings | None = None,
@@ -233,14 +231,13 @@ class SolidLanguageServer(ABC):
# For now, we assume that all language server implementations have the same signature of the constructor
# (which, unfortunately, differs from the signature of the base class).
# If this assumption is ever violated, we need branching logic here.
ls = ls_class(config, logger, repository_root_path, solidlsp_settings) # type: ignore
ls = ls_class(config, repository_root_path, solidlsp_settings) # type: ignore
ls.set_request_timeout(timeout)
return ls
def __init__(
self,
config: LanguageServerConfig,
logger: LanguageServerLogger,
repository_root_path: str,
process_launch_info: ProcessLaunchInfo,
language_id: str,
@@ -253,7 +250,6 @@ class SolidLanguageServer(ABC):
Do not instantiate this class directly. Use `LanguageServer.create` method instead.
:param config: The Multilspy configuration.
:param logger: The logger to use.
:param repository_root_path: The root path of the repository.
:param process_launch_info: Each language server has a specific command used to start the server.
This parameter is the command to launch the language server process.
@@ -267,16 +263,11 @@ class SolidLanguageServer(ABC):
self._solidlsp_settings = solidlsp_settings
lang = self.get_language_enum_instance()
self._custom_settings = solidlsp_settings.get_ls_specific_settings(lang)
logger.log(
f"Custom config (LS-specific settings) for {lang}: {self._custom_settings}",
logging.DEBUG,
)
log.debug(f"Custom config (LS-specific settings) for {lang}: {self._custom_settings}")
self._encoding = config.encoding
self.logger = logger
self.repository_root_path: str = repository_root_path
self.logger.log(
f"Creating language server instance for {repository_root_path=} with {language_id=} and process launch info: {process_launch_info}",
logging.DEBUG,
log.debug(
f"Creating language server instance for {repository_root_path=} with {language_id=} and process launch info: {process_launch_info}"
)
self.language_id = language_id
@@ -305,16 +296,14 @@ class SolidLanguageServer(ABC):
if config.trace_lsp_communication:
def logging_fn(source: str, target: str, msg: StringDict | str) -> None:
self.logger.log(f"LSP: {source} -> {target}: {msg!s}", self.logger.logger.level)
log.debug(f"LSP: {source} -> {target}: {msg!s}")
else:
logging_fn = None # type: ignore
# cmd is obtained from the child classes, which provide the language specific command to start the language server
# LanguageServerHandler provides the functionality to start the language server and communicate with it
self.logger.log(
f"Creating language server instance with {language_id=} and process launch info: {process_launch_info}", logging.DEBUG
)
log.debug(f"Creating language server instance with {language_id=} and process launch info: {process_launch_info}")
self.server = SolidLanguageServerHandler(
process_launch_info,
language=self.language,
@@ -330,7 +319,7 @@ class SolidLanguageServer(ABC):
# Normalize separators (pathspec expects forward slashes)
pattern = pattern.replace(os.path.sep, "/")
processed_patterns.append(pattern)
self.logger.log(f"Processing {len(processed_patterns)} ignored paths from the config", logging.DEBUG)
log.debug(f"Processing {len(processed_patterns)} ignored paths from the config", logging.DEBUG)
# Create a pathspec matcher from the processed patterns
self._ignore_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, processed_patterns)
@@ -407,20 +396,20 @@ class SolidLanguageServer(ABC):
by explicitly closing all I/O pipes.
"""
if not self.server.is_running():
self.logger.log("Server process not running, skipping shutdown.", logging.DEBUG)
log.debug("Server process not running, skipping shutdown.")
return
self.logger.log(f"Initiating final robust shutdown with a {timeout}s timeout...", logging.INFO)
log.info(f"Initiating final robust shutdown with a {timeout}s timeout...")
process = self.server.process
if process is None:
self.logger.log("Server process is None, cannot shutdown.", logging.DEBUG)
log.debug("Server process is None, cannot shutdown.")
return
# --- Main Shutdown Logic ---
# Stage 1: Graceful Termination Request
# Send LSP shutdown and close stdin to signal no more input.
try:
self.logger.log("Sending LSP shutdown request...", logging.DEBUG)
log.debug("Sending LSP shutdown request...")
# Use a thread to timeout the LSP shutdown call since it can hang
shutdown_thread = threading.Thread(target=self.server.shutdown)
shutdown_thread.daemon = True
@@ -428,37 +417,37 @@ class SolidLanguageServer(ABC):
shutdown_thread.join(timeout=2.0) # 2 second timeout for LSP shutdown
if shutdown_thread.is_alive():
self.logger.log("LSP shutdown request timed out, proceeding to terminate...", logging.DEBUG)
log.debug("LSP shutdown request timed out, proceeding to terminate...")
else:
self.logger.log("LSP shutdown request completed.", logging.DEBUG)
log.debug("LSP shutdown request completed.")
if process.stdin and not process.stdin.closed:
process.stdin.close()
self.logger.log("Stage 1 shutdown complete.", logging.DEBUG)
log.debug("Stage 1 shutdown complete.")
except Exception as e:
self.logger.log(f"Exception during graceful shutdown: {e}", logging.DEBUG)
log.debug(f"Exception during graceful shutdown: {e}")
# Ignore errors here, we are proceeding to terminate anyway.
# Stage 2: Terminate and Wait for Process to Exit
self.logger.log(f"Terminating process {process.pid}, current status: {process.poll()}", logging.DEBUG)
log.debug(f"Terminating process {process.pid}, current status: {process.poll()}")
process.terminate()
# Stage 3: Wait for process termination with timeout
try:
self.logger.log(f"Waiting for process {process.pid} to terminate...", logging.DEBUG)
log.debug(f"Waiting for process {process.pid} to terminate...")
exit_code = process.wait(timeout=timeout)
self.logger.log(f"Language server process terminated successfully with exit code {exit_code}.", logging.INFO)
log.info(f"Language server process terminated successfully with exit code {exit_code}.")
except subprocess.TimeoutExpired:
# If termination failed, forcefully kill the process
self.logger.log(f"Process {process.pid} termination timed out, killing process forcefully...", logging.WARNING)
log.warning(f"Process {process.pid} termination timed out, killing process forcefully...")
process.kill()
try:
exit_code = process.wait(timeout=2.0)
self.logger.log(f"Language server process killed successfully with exit code {exit_code}.", logging.INFO)
log.info(f"Language server process killed successfully with exit code {exit_code}.")
except subprocess.TimeoutExpired:
self.logger.log(f"Process {process.pid} could not be killed within timeout.", logging.ERROR)
log.error(f"Process {process.pid} could not be killed within timeout.")
except Exception as e:
self.logger.log(f"Error during process shutdown: {e}", logging.ERROR)
log.error(f"Error during process shutdown: {e}")
@contextmanager
def start_server(self) -> Iterator["SolidLanguageServer"]:
@@ -482,10 +471,7 @@ class SolidLanguageServer(ABC):
:param relative_file_path: The relative path of the file to open.
"""
if not self.server_started:
self.logger.log(
"open_file called before Language Server started",
logging.ERROR,
)
log.error("open_file called before Language Server started")
raise SolidLSPException("Language Server not started")
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
@@ -552,10 +538,7 @@ class SolidLanguageServer(ABC):
:param text_to_be_inserted: The text to insert.
"""
if not self.server_started:
self.logger.log(
"insert_text_at_position called before Language Server started",
logging.ERROR,
)
log.error("insert_text_at_position called before Language Server started")
raise SolidLSPException("Language Server not started")
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
@@ -598,10 +581,7 @@ class SolidLanguageServer(ABC):
Delete text between the given start and end positions in the given file and return the deleted text.
"""
if not self.server_started:
self.logger.log(
"insert_text_at_position called before Language Server started",
logging.ERROR,
)
log.error("insert_text_at_position called before Language Server started")
raise SolidLSPException("Language Server not started")
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
@@ -642,10 +622,7 @@ class SolidLanguageServer(ABC):
:return List[multilspy_types.Location]: A list of locations where the symbol is defined
"""
if not self.server_started:
self.logger.log(
"request_definition called before Language Server started",
logging.ERROR,
)
log.error("request_definition called before language server started")
raise SolidLSPException("Language Server not started")
if not self._has_waited_for_cross_file_references:
@@ -703,10 +680,7 @@ class SolidLanguageServer(ABC):
elif response is None:
# Some language servers return None when they cannot find a definition
# This is expected for certain symbol types like generics or types with incomplete information
self.logger.log(
f"Language server returned None for definition request at {relative_file_path}:{line}:{column}",
logging.WARNING,
)
log.warning(f"Language server returned None for definition request at {relative_file_path}:{line}:{column}")
else:
assert False, f"Unexpected response from Language Server: {response}"
@@ -735,10 +709,7 @@ class SolidLanguageServer(ABC):
:return: A list of locations where the symbol is referenced (excluding ignored directories)
"""
if not self.server_started:
self.logger.log(
"request_references called before Language Server started",
logging.ERROR,
)
log.error("request_references called before Language Server started")
raise SolidLSPException("Language Server not started")
if not self._has_waited_for_cross_file_references:
@@ -770,16 +741,15 @@ class SolidLanguageServer(ABC):
abs_path = PathUtils.uri_to_path(item[LSPConstants.URI]) # type: ignore
if not Path(abs_path).is_relative_to(self.repository_root_path):
self.logger.log(
log.warning(
"Found a reference in a path outside the repository, probably the LS is parsing things in installed packages or in the standardlib! "
f"Path: {abs_path}. This is a bug but we currently simply skip these references.",
logging.WARNING,
f"Path: {abs_path}. This is a bug but we currently simply skip these references."
)
continue
rel_path = Path(abs_path).relative_to(self.repository_root_path)
if self.is_ignored_path(str(rel_path)):
self.logger.log(f"Ignoring reference in {rel_path} since it should be ignored", logging.DEBUG)
log.debug("Ignoring reference in %s since it should be ignored", rel_path)
continue
new_item: dict = {}
@@ -800,10 +770,7 @@ class SolidLanguageServer(ABC):
:return: A list of diagnostics for the file
"""
if not self.server_started:
self.logger.log(
"request_text_document_diagnostics called before Language Server started",
logging.ERROR,
)
log.error("request_text_document_diagnostics called before Language Server started")
raise SolidLSPException("Language Server not started")
with self.open_file(relative_file_path):
@@ -983,7 +950,7 @@ class SolidLanguageServer(ABC):
return response
# no cached result, query language server
self.logger.log(f"Requesting document symbols for {relative_file_path} from the Language Server", logging.DEBUG)
log.debug(f"Requesting document symbols for {relative_file_path} from the Language Server")
response = self.server.send.document_symbol(
{"textDocument": {"uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri()}}
)
@@ -1420,10 +1387,7 @@ class SolidLanguageServer(ABC):
:return: List of objects containing the symbol and the location of the reference.
"""
if not self.server_started:
self.logger.log(
"request_referencing_symbols called before Language Server started",
logging.ERROR,
)
log.error("request_referencing_symbols called before Language Server started")
raise SolidLSPException("Language Server not started")
# First, get all references to the symbol
@@ -1470,10 +1434,7 @@ class SolidLanguageServer(ABC):
# We failed retrieving the symbol, falling back to creating a file symbol
if containing_symbol is None and include_file_symbols:
self.logger.log(
f"Could not find containing symbol for {ref_path}:{ref_line}:{ref_col}. Returning file symbol instead",
logging.WARNING,
)
log.warning(f"Could not find containing symbol for {ref_path}:{ref_line}:{ref_col}. Returning file symbol instead")
fileRange = self._get_range_from_file_content(file_data.contents)
location = ls_types.Location(
uri=str(pathlib.Path(os.path.join(self.repository_root_path, ref_path)).as_uri()),
@@ -1513,7 +1474,7 @@ class SolidLanguageServer(ABC):
if include_self:
result.append(ReferenceInSymbol(symbol=containing_symbol, line=ref_line, character=ref_col))
continue
self.logger.log(f"Found self-reference for {incoming_symbol['name']}, skipping it since {include_self=}", logging.DEBUG)
log.debug(f"Found self-reference for {incoming_symbol['name']}, skipping it since {include_self=}")
continue
# checking whether reference is an import
@@ -1526,10 +1487,9 @@ class SolidLanguageServer(ABC):
and containing_symbol["name"] == incoming_symbol["name"]
and containing_symbol["kind"] == incoming_symbol["kind"]
):
self.logger.log(
log.debug(
f"Found import of referenced symbol {incoming_symbol['name']}"
f"in {containing_symbol['location']['relativePath']}, skipping",
logging.DEBUG,
f"in {containing_symbol['location']['relativePath']}, skipping"
)
continue
@@ -1576,10 +1536,7 @@ class SolidLanguageServer(ABC):
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
content = FileUtils.read_file(absolute_file_path, self._encoding)
if content.split("\n")[line].strip() == "":
self.logger.log(
f"Passing empty lines to request_container_symbol is currently not supported, {relative_file_path=}, {line=}",
logging.ERROR,
)
log.error(f"Passing empty lines to request_container_symbol is currently not supported, {relative_file_path=}, {line=}")
return None
document_symbols = self.request_document_symbols(relative_file_path)
@@ -1693,10 +1650,7 @@ class SolidLanguageServer(ABC):
:return: The symbol information for the definition, or None if not found.
"""
if not self.server_started:
self.logger.log(
"request_defining_symbol called before Language Server started",
logging.ERROR,
)
log.error("request_defining_symbol called before language server started")
raise SolidLSPException("Language Server not started")
# Get the definition location(s)
@@ -1773,9 +1727,7 @@ class SolidLanguageServer(ABC):
saved_cache = load_cache(str(cache_file), self._raw_document_symbols_cache_version())
if saved_cache is not None:
self._raw_document_symbols_cache = saved_cache
self.logger.log(
f"Loaded {len(self._raw_document_symbols_cache)} entries from raw document symbols cache.", logging.INFO
)
log.info(f"Loaded {len(self._raw_document_symbols_cache)} entries from raw document symbols cache.")
except Exception as e:
# cache can become corrupt, so just skip loading it
log.warning(
@@ -1810,7 +1762,7 @@ class SolidLanguageServer(ABC):
saved_cache = load_cache(str(cache_file), self.DOCUMENT_SYMBOL_CACHE_VERSION)
if saved_cache is not None:
self._document_symbols_cache = saved_cache
self.logger.log(f"Loaded {len(self._document_symbols_cache)} entries from document symbols cache.", logging.INFO)
log.info(f"Loaded {len(self._document_symbols_cache)} entries from document symbols cache.")
except Exception as e:
# cache can become corrupt, so just skip loading it
log.warning(
@@ -1902,10 +1854,7 @@ class SolidLanguageServer(ABC):
:return: self for method chaining
"""
self.logger.log(
f"Starting language server with language {self.language_server.language} for {self.language_server.repository_root_path}",
logging.INFO,
)
log.info(f"Starting language server with language {self.language_server.language} for {self.language_server.repository_root_path}")
self._start_server_process()
return self
@@ -1919,7 +1868,7 @@ class SolidLanguageServer(ABC):
try:
self._shutdown(timeout=shutdown_timeout)
except Exception as e:
self.logger.log(f"Exception while shutting down language server: {e}", logging.WARNING)
log.warning(f"Exception while shutting down language server: {e}")
@property
def language_server(self) -> Self:
-66
View File
@@ -1,66 +0,0 @@
"""
Multilspy logger module.
"""
import inspect
import logging
from datetime import datetime
from pydantic import BaseModel
class LogLine(BaseModel):
"""
Represents a line in the Multilspy log
"""
time: str
level: str
caller_file: str
caller_name: str
caller_line: int
message: str
class LanguageServerLogger:
"""
Logger class
"""
def __init__(self, json_format: bool = False, log_level: int = logging.INFO) -> None:
self.logger = logging.getLogger("solidlsp")
self.logger.setLevel(log_level)
self.json_format = json_format
def log(self, debug_message: str, level: int, sanitized_error_message: str = "", stacklevel: int = 2) -> None:
"""
Log the debug and sanitized messages using the logger
"""
debug_message = debug_message.replace("'", '"').replace("\n", " ")
sanitized_error_message = sanitized_error_message.replace("'", '"').replace("\n", " ")
# Collect details about the callee
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
caller_file = calframe[1][1].split("/")[-1]
caller_line = calframe[1][2]
caller_name = calframe[1][3]
if self.json_format:
# Construct the debug log line
debug_log_line = LogLine(
time=str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
level=logging.getLevelName(level),
caller_file=caller_file,
caller_name=caller_name,
caller_line=caller_line,
message=debug_message,
)
self.logger.log(
level=level,
msg=debug_log_line.json(),
stacklevel=stacklevel,
)
else:
self.logger.log(level, debug_message, stacklevel=stacklevel)
+7 -8
View File
@@ -17,7 +17,6 @@ import charset_normalizer
import requests
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_types import UnifiedSymbolInformation
log = logging.getLogger(__name__)
@@ -201,7 +200,7 @@ class FileUtils:
raise exc
@staticmethod
def download_file(logger: LanguageServerLogger, url: str, target_path: str) -> None:
def download_file(url: str, target_path: str) -> None:
"""
Downloads the file from the given URL to the given {target_path}
"""
@@ -209,16 +208,16 @@ class FileUtils:
try:
response = requests.get(url, stream=True, timeout=60)
if response.status_code != 200:
logger.log(f"Error downloading file '{url}': {response.status_code} {response.text}", logging.ERROR)
log.error(f"Error downloading file '{url}': {response.status_code} {response.text}")
raise SolidLSPException("Error downloading file.")
with open(target_path, "wb") as f:
shutil.copyfileobj(response.raw, f)
except Exception as exc:
logger.log(f"Error downloading file '{url}': {exc}", logging.ERROR)
log.error(f"Error downloading file '{url}': {exc}")
raise SolidLSPException("Error downloading file.") from None
@staticmethod
def download_and_extract_archive(logger: LanguageServerLogger, url: str, target_path: str, archive_type: str) -> None:
def download_and_extract_archive(url: str, target_path: str, archive_type: str) -> None:
"""
Downloads the archive from the given URL having format {archive_type} and extracts it to the given {target_path}
"""
@@ -227,7 +226,7 @@ class FileUtils:
tmp_file_name = str(PurePath(os.path.expanduser("~"), "multilspy_tmp", uuid.uuid4().hex))
tmp_files.append(tmp_file_name)
os.makedirs(os.path.dirname(tmp_file_name), exist_ok=True)
FileUtils.download_file(logger, url, tmp_file_name)
FileUtils.download_file(url, tmp_file_name)
if archive_type in ["tar", "gztar", "bztar", "xztar"]:
os.makedirs(target_path, exist_ok=True)
shutil.unpack_archive(tmp_file_name, target_path, archive_type)
@@ -258,10 +257,10 @@ class FileUtils:
# For single binary files, just move to target without extraction
shutil.move(tmp_file_name, target_path)
else:
logger.log(f"Unknown archive type '{archive_type}' for extraction", logging.ERROR)
log.error(f"Unknown archive type '{archive_type}' for extraction")
raise SolidLSPException(f"Unknown archive type '{archive_type}'")
except Exception as exc:
logger.log(f"Error extracting archive '{tmp_file_name}' obtained from '{url}': {exc}", logging.ERROR)
log.error(f"Error extracting archive '{tmp_file_name}' obtained from '{url}': {exc}")
raise SolidLSPException("Error extracting archive.") from exc
finally:
for tmp_file_name in tmp_files:
-3
View File
@@ -9,7 +9,6 @@ from serena.project import Project
from serena.util.file_system import GitignoreParser
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.settings import SolidLSPSettings
configure(level=logging.ERROR)
@@ -44,10 +43,8 @@ def create_ls(
for spec in gitignore_parser.get_ignore_specs():
ignored_paths.extend(spec.patterns)
config = LanguageServerConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication)
logger = LanguageServerLogger(log_level=log_level)
return SolidLanguageServer.create(
config,
logger,
repo_path,
solidlsp_settings=SolidLSPSettings(solidlsp_dir=SERENA_MANAGED_DIR_IN_HOME, project_data_relative_path=SERENA_MANAGED_DIR_NAME),
)
+18 -12
View File
@@ -5,6 +5,7 @@ from typing import cast
from unittest.mock import Mock, patch
import pytest
from sensai.util import logging
from solidlsp import SolidLanguageServer
from solidlsp.language_servers.csharp_language_server import (
@@ -247,8 +248,6 @@ class TestCSharpSolutionProjectOpening:
solution_file = temp_path / "TestSolution.sln"
solution_file.touch()
# Mock logger to capture log messages
mock_logger = Mock()
mock_config = Mock(spec=LanguageServerConfig)
mock_config.ignored_paths = []
@@ -256,10 +255,15 @@ class TestCSharpSolutionProjectOpening:
mock_settings = Mock(spec=SolidLSPSettings)
mock_settings.ls_resources_dir = "/tmp/test_ls_resources"
mock_settings.project_data_relative_path = "project_data"
CSharpLanguageServer(mock_config, mock_logger, str(temp_path), mock_settings)
# Verify that logger was called with solution file discovery
mock_logger.log.assert_any_call(f"Found solution/project file: {solution_file}", 20) # logging.INFO
with logging.SuspendedLoggersContext():
logging.getLogger().setLevel(logging.DEBUG)
with logging.MemoryLoggerContext() as mem_log:
CSharpLanguageServer(mock_config, str(temp_path), mock_settings)
# Verify that logger was called with solution file discovery
expected_log_msg = f"Found solution/project file: {solution_file}"
assert expected_log_msg in mem_log.get_log()
@patch("solidlsp.language_servers.csharp_language_server.CSharpLanguageServer._ensure_server_installed")
@patch("solidlsp.language_servers.csharp_language_server.CSharpLanguageServer._start_server")
@@ -273,20 +277,22 @@ class TestCSharpSolutionProjectOpening:
temp_path = Path(temp_dir)
# Mock logger to capture log messages
mock_logger = Mock()
mock_config = Mock(spec=LanguageServerConfig)
mock_config.ignored_paths = []
# Create CSharpLanguageServer instance
mock_settings = Mock(spec=SolidLSPSettings)
mock_settings.ls_resources_dir = "/tmp/test_ls_resources"
mock_settings.project_data_relative_path = "project_data"
CSharpLanguageServer(mock_config, mock_logger, str(temp_path), mock_settings)
# Verify that logger was called with warning about no solution/project files
mock_logger.log.assert_any_call(
"No .sln or .csproj file found, language server will attempt auto-discovery", 30 # logging.WARNING
)
# Create CSharpLanguageServer instance
with logging.SuspendedLoggersContext():
logging.getLogger().setLevel(logging.DEBUG)
with logging.MemoryLoggerContext() as mem_log:
CSharpLanguageServer(mock_config, str(temp_path), mock_settings)
# Verify that logger was called with warning about no solution/project files
expected_log_msg = "No .sln or .csproj file found, language server will attempt auto-discovery"
assert expected_log_msg in mem_log.get_log()
def test_solution_and_project_opening_with_real_test_repo(self):
"""Test solution and project opening with the actual C# test repository."""
@@ -1,11 +1,9 @@
import logging
import os
import pytest
from solidlsp.language_servers.scala_language_server import ScalaLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.settings import SolidLSPSettings
pytest.skip("Scala must be compiled for these tests to run through, which is a huge hassle", allow_module_level=True)
@@ -18,10 +16,9 @@ pytestmark = pytest.mark.scala
@pytest.fixture(scope="module")
def scala_ls():
repo_root = os.path.abspath("test/resources/repos/scala")
logger = LanguageServerLogger(json_format=False, log_level=logging.INFO)
config = LanguageServerConfig(code_language=Language.SCALA)
solidlsp_settings = SolidLSPSettings()
ls = ScalaLanguageServer(config, logger, repo_root, solidlsp_settings)
ls = ScalaLanguageServer(config, repo_root, solidlsp_settings)
with ls.start_server():
yield ls