diff --git a/.gitignore b/.gitignore index 89bbe0b2..78b8833a 100644 --- a/.gitignore +++ b/.gitignore @@ -202,20 +202,12 @@ pylint.html /agent-ui /test/**/.serena -# dynamic LS installations -/src/multilspy/language_servers/*/static -/src/solidlsp/language_servers/*/static -/src/solidlsp/language_servers/static - # clojure-lsp temporary files .calva/ .clj-kondo/ .cpcache/ .lsp/ -# Cache is in .multilspy -**/.multilspy/** - # temporary and backup files *.bak *.tmp diff --git a/.serena/memories/adding_new_language_support_guide.md b/.serena/memories/adding_new_language_support_guide.md index 21980531..18d12436 100644 --- a/.serena/memories/adding_new_language_support_guide.md +++ b/.serena/memories/adding_new_language_support_guide.md @@ -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 diff --git a/src/serena/ls_manager.py b/src/serena/ls_manager.py index a3072286..106cb3ed 100644 --- a/src/serena/ls_manager.py +++ b/src/serena/ls_manager.py @@ -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( diff --git a/src/serena/project.py b/src/serena/project.py index 3cd5eb08..0a23a4a6 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -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) diff --git a/src/serena/util/logging.py b/src/serena/util/logging.py index 75af996b..dae83c0a 100644 --- a/src/serena/util/logging.py +++ b/src/serena/util/logging.py @@ -1,11 +1,14 @@ import queue import threading from collections.abc import Callable +from typing import Optional from sensai.util import logging from serena.constants import SERENA_LOG_FORMAT +lg = logging + class MemoryLogHandler(logging.Handler): def __init__(self, level: int = logging.NOTSET) -> None: @@ -65,3 +68,42 @@ class LogBuffer: def get_log_messages(self) -> list[str]: with self._lock: return self._log_messages.copy() + + +class SuspendedLoggersContext: + """A context manager that provides an isolated logging environment. + + Temporarily removes all root log handlers upon entry, providing a clean slate + for defining new log handlers within the context. Upon exit, restores the original + logging configuration. This is useful when you need to temporarily configure + an isolated logging setup with well-defined log handlers. + + The context manager: + - Removes all existing (root) log handlers on entry + - Allows defining new temporary handlers within the context + - Restores the original configuration (handlers and root log level) on exit + + Example: + >>> with SuspendedLoggersContext(): + ... # No handlers are active here (configure your own and set desired log level) + ... pass + >>> # Original log handlers are restored here + + """ + + def __init__(self) -> None: + self.saved_root_handlers: list = [] + self.saved_root_level: Optional[int] = None + + def __enter__(self) -> "SuspendedLoggersContext": + root_logger = lg.getLogger() + self.saved_root_handlers = root_logger.handlers.copy() + self.saved_root_level = root_logger.level + root_logger.handlers.clear() + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore + root_logger = lg.getLogger() + root_logger.handlers = self.saved_root_handlers + if self.saved_root_level is not None: + root_logger.setLevel(self.saved_root_level) diff --git a/src/solidlsp/language_servers/al_language_server.py b/src/solidlsp/language_servers/al_language_server.py index b98551bd..d6052a2a 100644 --- a/src/solidlsp/language_servers/al_language_server.py +++ b/src/solidlsp/language_servers/al_language_server.py @@ -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 diff --git a/src/solidlsp/language_servers/bash_language_server.py b/src/solidlsp/language_servers/bash_language_server.py index 6759b5a6..870e08b6 100644 --- a/src/solidlsp/language_servers/bash_language_server.py +++ b/src/solidlsp/language_servers/bash_language_server.py @@ -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 diff --git a/src/solidlsp/language_servers/clangd_language_server.py b/src/solidlsp/language_servers/clangd_language_server.py index d4f67e9b..eccd6017 100644 --- a/src/solidlsp/language_servers/clangd_language_server.py +++ b/src/solidlsp/language_servers/clangd_language_server.py @@ -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"] diff --git a/src/solidlsp/language_servers/clojure_lsp.py b/src/solidlsp/language_servers/clojure_lsp.py index 241e57a0..f32a4d5a 100644 --- a/src/solidlsp/language_servers/clojure_lsp.py +++ b/src/solidlsp/language_servers/clojure_lsp.py @@ -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"] diff --git a/src/solidlsp/language_servers/common.py b/src/solidlsp/language_servers/common.py index c1cea673..504b0637 100644 --- a/src/solidlsp/language_servers/common.py +++ b/src/solidlsp/language_servers/common.py @@ -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: diff --git a/src/solidlsp/language_servers/csharp_language_server.py b/src/solidlsp/language_servers/csharp_language_server.py index 2382b761..b2aec7db 100644 --- a/src/solidlsp/language_servers/csharp_language_server.py +++ b/src/solidlsp/language_servers/csharp_language_server.py @@ -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: diff --git a/src/solidlsp/language_servers/dart_language_server.py b/src/solidlsp/language_servers/dart_language_server.py index 0135ae7a..12dc3143 100644 --- a/src/solidlsp/language_servers/dart_language_server.py +++ b/src/solidlsp/language_servers/dart_language_server.py @@ -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({}) diff --git a/src/solidlsp/language_servers/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls.py index 7c718c07..d058521e 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls.py @@ -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"] diff --git a/src/solidlsp/language_servers/elixir_tools/elixir_tools.py b/src/solidlsp/language_servers/elixir_tools/elixir_tools.py index fa14d3e0..926ee792 100644 --- a/src/solidlsp/language_servers/elixir_tools/elixir_tools.py +++ b/src/solidlsp/language_servers/elixir_tools/elixir_tools.py @@ -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) diff --git a/src/solidlsp/language_servers/elm_language_server.py b/src/solidlsp/language_servers/elm_language_server.py index f75af40c..0a4c25bd 100644 --- a/src/solidlsp/language_servers/elm_language_server.py +++ b/src/solidlsp/language_servers/elm_language_server.py @@ -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: diff --git a/src/solidlsp/language_servers/erlang_language_server.py b/src/solidlsp/language_servers/erlang_language_server.py index 36651688..065f96bf 100644 --- a/src/solidlsp/language_servers/erlang_language_server.py +++ b/src/solidlsp/language_servers/erlang_language_server.py @@ -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: diff --git a/src/solidlsp/language_servers/fortran_language_server.py b/src/solidlsp/language_servers/fortran_language_server.py index 993c21fa..406ab182 100644 --- a/src/solidlsp/language_servers/fortran_language_server.py +++ b/src/solidlsp/language_servers/fortran_language_server.py @@ -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") diff --git a/src/solidlsp/language_servers/gopls.py b/src/solidlsp/language_servers/gopls.py index 9f9aec7a..afb628b9 100644 --- a/src/solidlsp/language_servers/gopls.py +++ b/src/solidlsp/language_servers/gopls.py @@ -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 diff --git a/src/solidlsp/language_servers/haskell_language_server.py b/src/solidlsp/language_servers/haskell_language_server.py index f1353a34..e4e13bba 100644 --- a/src/solidlsp/language_servers/haskell_language_server.py +++ b/src/solidlsp/language_servers/haskell_language_server.py @@ -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") diff --git a/src/solidlsp/language_servers/intelephense.py b/src/solidlsp/language_servers/intelephense.py index dc62a70e..64f80e03 100644 --- a/src/solidlsp/language_servers/intelephense.py +++ b/src/solidlsp/language_servers/intelephense.py @@ -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. """ @@ -52,7 +53,7 @@ class Intelephense(SolidLanguageServer): PlatformId.WIN_x64, PlatformId.WIN_arm64, ] - assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for multilspy PHP at the moment" + assert platform_id in valid_platforms, f"Platform {platform_id} is not supported by {cls.__name__} at the moment" # Verify both node and npm are installed is_node_installed = shutil.which("node") is not None @@ -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"] diff --git a/src/solidlsp/language_servers/jedi_server.py b/src/solidlsp/language_servers/jedi_server.py index f08cd09a..1352cb37 100644 --- a/src/solidlsp/language_servers/jedi_server.py +++ b/src/solidlsp/language_servers/jedi_server.py @@ -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"] diff --git a/src/solidlsp/language_servers/julia_server.py b/src/solidlsp/language_servers/julia_server.py index 178ac91b..f255a539 100644 --- a/src/solidlsp/language_servers/julia_server.py +++ b/src/solidlsp/language_servers/julia_server.py @@ -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.") diff --git a/src/solidlsp/language_servers/kotlin_language_server.py b/src/solidlsp/language_servers/kotlin_language_server.py index c1c6b975..cd3624fd 100644 --- a/src/solidlsp/language_servers/kotlin_language_server.py +++ b/src/solidlsp/language_servers/kotlin_language_server.py @@ -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"] diff --git a/src/solidlsp/language_servers/lua_ls.py b/src/solidlsp/language_servers/lua_ls.py index 0ff6b7c4..8802ee87 100644 --- a/src/solidlsp/language_servers/lua_ls.py +++ b/src/solidlsp/language_servers/lua_ls.py @@ -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 diff --git a/src/solidlsp/language_servers/marksman.py b/src/solidlsp/language_servers/marksman.py index 85919113..d799abb2 100644 --- a/src/solidlsp/language_servers/marksman.py +++ b/src/solidlsp/language_servers/marksman.py @@ -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() diff --git a/src/solidlsp/language_servers/nixd_ls.py b/src/solidlsp/language_servers/nixd_ls.py index edf092e3..d3e3e96a 100644 --- a/src/solidlsp/language_servers/nixd_ls.py +++ b/src/solidlsp/language_servers/nixd_ls.py @@ -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 diff --git a/src/solidlsp/language_servers/omnisharp.py b/src/solidlsp/language_servers/omnisharp.py index 5c5be862..83bfae10 100644 --- a/src/solidlsp/language_servers/omnisharp.py +++ b/src/solidlsp/language_servers/omnisharp.py @@ -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: diff --git a/src/solidlsp/language_servers/perl_language_server.py b/src/solidlsp/language_servers/perl_language_server.py index 5937b6a4..b7ab42a0 100644 --- a/src/solidlsp/language_servers/perl_language_server.py +++ b/src/solidlsp/language_servers/perl_language_server.py @@ -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") diff --git a/src/solidlsp/language_servers/pyright_server.py b/src/solidlsp/language_servers/pyright_server.py index 10cc7886..649dad24 100644 --- a/src/solidlsp/language_servers/pyright_server.py +++ b/src/solidlsp/language_servers/pyright_server.py @@ -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() diff --git a/src/solidlsp/language_servers/r_language_server.py b/src/solidlsp/language_servers/r_language_server.py index 467cc144..9802381d 100644 --- a/src/solidlsp/language_servers/r_language_server.py +++ b/src/solidlsp/language_servers/r_language_server.py @@ -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() diff --git a/src/solidlsp/language_servers/regal_server.py b/src/solidlsp/language_servers/regal_server.py index eb6abc1d..35c7bee1 100644 --- a/src/solidlsp/language_servers/regal_server.py +++ b/src/solidlsp/language_servers/regal_server.py @@ -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) diff --git a/src/solidlsp/language_servers/ruby_lsp.py b/src/solidlsp/language_servers/ruby_lsp.py index 95c9f7b6..744213fe 100644 --- a/src/solidlsp/language_servers/ruby_lsp.py +++ b/src/solidlsp/language_servers/ruby_lsp.py @@ -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() diff --git a/src/solidlsp/language_servers/rust_analyzer.py b/src/solidlsp/language_servers/rust_analyzer.py index aa5617c5..7791b3f1 100644 --- a/src/solidlsp/language_servers/rust_analyzer.py +++ b/src/solidlsp/language_servers/rust_analyzer.py @@ -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"] diff --git a/src/solidlsp/language_servers/scala_language_server.py b/src/solidlsp/language_servers/scala_language_server.py index 5911c453..79a0e205 100644 --- a/src/solidlsp/language_servers/scala_language_server.py +++ b/src/solidlsp/language_servers/scala_language_server.py @@ -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) diff --git a/src/solidlsp/language_servers/solargraph.py b/src/solidlsp/language_servers/solargraph.py index 61249cde..4915f730 100644 --- a/src/solidlsp/language_servers/solargraph.py +++ b/src/solidlsp/language_servers/solargraph.py @@ -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() diff --git a/src/solidlsp/language_servers/sourcekit_lsp.py b/src/solidlsp/language_servers/sourcekit_lsp.py index d21eac4d..ad004d5d 100644 --- a/src/solidlsp/language_servers/sourcekit_lsp.py +++ b/src/solidlsp/language_servers/sourcekit_lsp.py @@ -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) diff --git a/src/solidlsp/language_servers/terraform_ls.py b/src/solidlsp/language_servers/terraform_ls.py index 66fd01a1..2b54c0fa 100644 --- a/src/solidlsp/language_servers/terraform_ls.py +++ b/src/solidlsp/language_servers/terraform_ls.py @@ -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 diff --git a/src/solidlsp/language_servers/typescript_language_server.py b/src/solidlsp/language_servers/typescript_language_server.py index 2ebcb869..10c5cc69 100644 --- a/src/solidlsp/language_servers/typescript_language_server.py +++ b/src/solidlsp/language_servers/typescript_language_server.py @@ -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() diff --git a/src/solidlsp/language_servers/vts_language_server.py b/src/solidlsp/language_servers/vts_language_server.py index 51922e70..21320374 100644 --- a/src/solidlsp/language_servers/vts_language_server.py +++ b/src/solidlsp/language_servers/vts_language_server.py @@ -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() diff --git a/src/solidlsp/language_servers/yaml_language_server.py b/src/solidlsp/language_servers/yaml_language_server.py index 6e2dc959..6ec52501 100644 --- a/src/solidlsp/language_servers/yaml_language_server.py +++ b/src/solidlsp/language_servers/yaml_language_server.py @@ -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() diff --git a/src/solidlsp/language_servers/zls.py b/src/solidlsp/language_servers/zls.py index 9ab8daac..f8019a4e 100644 --- a/src/solidlsp/language_servers/zls.py +++ b/src/solidlsp/language_servers/zls.py @@ -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}") diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index fcb0734f..f47f60e7 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -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, @@ -252,13 +249,11 @@ 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. - The command must pass appropriate flags to the binary, so that it runs in the stdio mode, - as opposed to HTTP, TCP modes supported by some language servers. + :param config: the global SolidLSP configuration. + :param repository_root_path: the root path of the repository. + :param process_launch_info: the command used to start the actual language server. + The command must pass appropriate flags to the binary, so that it runs in the stdio mode, + as opposed to HTTP, TCP modes supported by some language servers. :param cache_version_raw_document_symbols: the version, for caching, of the raw document symbols coming from this specific language server. This should be incremented by subclasses calling this constructor whenever the format of the raw document symbols changes (typically because the language server @@ -267,16 +262,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 +295,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 +318,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) @@ -354,10 +342,11 @@ class SolidLanguageServer(ABC): self.server.set_request_timeout(timeout) def get_ignore_spec(self) -> pathspec.PathSpec: - """Returns the pathspec matcher for the paths that were configured to be ignored through - the multilspy config. + """ + Returns the pathspec matcher for the paths that were configured to be ignored through + the language server configuration. - This is is a subset of the full language-specific ignore spec that determines + This is a subset of the full language-specific ignore spec that determines which files are relevant for the language server. This matcher is useful for operations outside of the language server, @@ -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)) @@ -639,13 +619,10 @@ class SolidLanguageServer(ABC): :param line: The line number of the symbol :param column: The column number of the symbol - :return List[multilspy_types.Location]: A list of locations where the symbol is defined + :return: the 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): @@ -875,7 +842,7 @@ class SolidLanguageServer(ABC): :param line: The line number of the symbol :param column: The column number of the symbol - :return List[multilspy_types.CompletionItem]: A list of completions + :return: A list of completions """ with self.open_file(relative_file_path): open_file_buffer = self.open_file_buffers[pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri()] @@ -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: diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py index 7a09588f..160205e1 100644 --- a/src/solidlsp/ls_config.py +++ b/src/solidlsp/ls_config.py @@ -28,7 +28,7 @@ class FilenameMatcher: class Language(str, Enum): """ - Possible languages with Multilspy. + Enumeration of language servers supported by SolidLSP. """ CSHARP = "csharp" @@ -351,9 +351,6 @@ class LanguageServerConfig: @classmethod def from_dict(cls, env: dict) -> Self: - """ - Create a MultilspyConfig instance from a dictionary - """ import inspect return cls(**{k: v for k, v in env.items() if k in inspect.signature(cls).parameters}) diff --git a/src/solidlsp/ls_logger.py b/src/solidlsp/ls_logger.py deleted file mode 100644 index 7aa0e0a6..00000000 --- a/src/solidlsp/ls_logger.py +++ /dev/null @@ -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) diff --git a/src/solidlsp/ls_types.py b/src/solidlsp/ls_types.py index a1baab88..003ede2a 100644 --- a/src/solidlsp/ls_types.py +++ b/src/solidlsp/ls_types.py @@ -1,5 +1,5 @@ """ -Defines wrapper objects around the types returned by LSP to ensure decoupling between LSP versions and multilspy +Defines wrapper objects around the types returned by LSP to ensure decoupling between LSP versions and SolidLSP """ from __future__ import annotations diff --git a/src/solidlsp/ls_utils.py b/src/solidlsp/ls_utils.py index 08e6a279..b8aa6845 100644 --- a/src/solidlsp/ls_utils.py +++ b/src/solidlsp/ls_utils.py @@ -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,25 +208,25 @@ 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} """ try: tmp_files = [] - tmp_file_name = str(PurePath(os.path.expanduser("~"), "multilspy_tmp", uuid.uuid4().hex)) + tmp_file_name = str(PurePath(os.path.expanduser("~"), "solidlsp_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: @@ -270,10 +269,6 @@ class FileUtils: class PlatformId(str, Enum): - """ - multilspy supported platforms - """ - WIN_x86 = "win-x86" WIN_x64 = "win-x64" WIN_arm64 = "win-arm64" @@ -291,10 +286,6 @@ class PlatformId(str, Enum): class DotnetVersion(str, Enum): - """ - multilspy supported dotnet versions - """ - V4 = "4" V6 = "6" V7 = "7" diff --git a/src/solidlsp/lsp_protocol_handler/server.py b/src/solidlsp/lsp_protocol_handler/server.py index 219135b7..d8ab2718 100644 --- a/src/solidlsp/lsp_protocol_handler/server.py +++ b/src/solidlsp/lsp_protocol_handler/server.py @@ -46,17 +46,25 @@ log = logging.getLogger(__name__) @dataclasses.dataclass class ProcessLaunchInfo: """ - This class is used to store the information required to launch a process. + This class is used to store the information required to launch a (language server) process. """ - # The command to launch the process cmd: str | list[str] + """ + the command used to launch the process. + Specification as a list is preferred (as it is more robust and avoids incorrect quoting of arguments); + the string variant is supported for backward compatibility only + """ - # The environment variables to set for the process env: dict[str, str] = dataclasses.field(default_factory=dict) + """ + the environment variables to set for the process + """ - # The working directory for the process cwd: str = os.getcwd() + """ + the working directory for the process + """ class LSPError(Exception): diff --git a/test/conftest.py b/test/conftest.py index 1ef2501c..8c8fb33a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -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), ) diff --git a/test/solidlsp/csharp/test_csharp_basic.py b/test/solidlsp/csharp/test_csharp_basic.py index 21ec4146..5bd7beb1 100644 --- a/test/solidlsp/csharp/test_csharp_basic.py +++ b/test/solidlsp/csharp/test_csharp_basic.py @@ -5,7 +5,9 @@ from typing import cast from unittest.mock import Mock, patch import pytest +from sensai.util import logging +from serena.util.logging import SuspendedLoggersContext from solidlsp import SolidLanguageServer from solidlsp.language_servers.csharp_language_server import ( CSharpLanguageServer, @@ -247,8 +249,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 +256,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 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 +278,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 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.""" diff --git a/test/solidlsp/scala/test_scala_language_server.py b/test/solidlsp/scala/test_scala_language_server.py index f22e2e96..9a25009a 100644 --- a/test/solidlsp/scala/test_scala_language_server.py +++ b/test/solidlsp/scala/test_scala_language_server.py @@ -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