diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a8fc82..f2eabb38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ Status of the `main` branch. Changes prior to the next official version change will appear here. * **Reduce the use of asyncio to a minimum**, improving stability and reducing the need for workarounds - * Switch to newly developed fully synchronous LSP library `solidlsp` (derived from `multilspy`) + * Switch to newly developed fully synchronous LSP library `solidlsp` (derived from `multilspy`), + removing our fork of `multilspy` (src/multilspy) * Switch from fastapi (which uses asyncio) to Flask in the Serena dashboard * The MCP server is the only asynchronous component now, which resolves cross-component loop contamination, such that process isolation is no longer required. diff --git a/README.md b/README.md index 1a5b7a51..d67000aa 100644 --- a/README.md +++ b/README.md @@ -414,7 +414,7 @@ For more information on MCP servers with Claude Desktop, see [the official quick Being an MCP Server, Serena can be included in any MCP Client. The same configuration as above, perhaps with small client-specific modifications, should work. Most of the popular existing coding assistants (IDE extensions or VSCode-like IDEs) support connections -to MCP Servers. It is ** recommended to use the `ide-assistant` context** for these integrations by adding `"--context", "ide-assistant"` to the `args` in your MCP client's configuration. Including Serena generally boosts their performance +to MCP Servers. It is **recommended to use the `ide-assistant` context** for these integrations by adding `"--context", "ide-assistant"` to the `args` in your MCP client's configuration. Including Serena generally boosts their performance by providing them tools for symbolic operations. In this case, the billing for the usage continues to be controlled by the client of your choice @@ -422,8 +422,8 @@ In this case, the billing for the usage continues to be controlled by the client e.g., for one of the following reasons: 1. You are already using a coding assistant (say Cline or Cursor) and just want to make it more powerful. -2. You are on Linux and don't want to use the [community-created Claude Desktop](https://github.com/aaddrick/claude-desktop-debian) -3. You want tighter integration of Serena into your IDE and don't mind paying for that +2. You are on Linux and don't want to use the [community-created Claude Desktop](https://github.com/aaddrick/claude-desktop-debian). +3. You want tighter integration of Serena into your IDE and don't mind paying for that. ### Agno Agent diff --git a/pyproject.toml b/pyproject.toml index a34dd303..d3ba68a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,6 @@ target-version = [ exclude = ''' /( src/multilspy - | src/solidlsp )/ ''' @@ -140,7 +139,6 @@ target-version = "py311" line-length = 140 exclude = [ "src/multilspy", - "src/solidlsp" ] [tool.ruff.format] @@ -240,6 +238,13 @@ ignore = [ "SIM110", # requires use of any(...) instead of for-loop "G001", # forbids str.format in log statements "E722", # forbids unspecific except clause + "SIM105", # forbids empty/general except clause + "SIM113", # wants to enforce use of enumerate + "E712", # forbids equality comparison with True/False + "UP007", # forbids some uses of Union + "TID252", # forbids relative imports + "B904", # forces use of raise from other_exception + "RUF012", # forbids mutable attributes as ClassVar ] unfixable = [ "F841", diff --git a/src/multilspy/.syncCommitId.remote b/src/multilspy/.syncCommitId.remote deleted file mode 100644 index 20a711e0..00000000 --- a/src/multilspy/.syncCommitId.remote +++ /dev/null @@ -1 +0,0 @@ -606542ed8766bfbcc6490b3115eb0aea78c693e5 \ No newline at end of file diff --git a/src/multilspy/.syncCommitId.this b/src/multilspy/.syncCommitId.this deleted file mode 100644 index 84678199..00000000 --- a/src/multilspy/.syncCommitId.this +++ /dev/null @@ -1 +0,0 @@ -2f5651bd7846ebb6abde121223233bf975ce98ee \ No newline at end of file diff --git a/src/multilspy/__init__.py b/src/multilspy/__init__.py deleted file mode 100644 index a8229e9f..00000000 --- a/src/multilspy/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -This module contains the multilspy API -""" - -from . import multilspy_types as Types -from .language_server import LanguageServer, SyncLanguageServer - -__all__ = ["LanguageServer", "Types", "SyncLanguageServer"] diff --git a/src/multilspy/language_server.py b/src/multilspy/language_server.py deleted file mode 100644 index db9c1cbe..00000000 --- a/src/multilspy/language_server.py +++ /dev/null @@ -1,2344 +0,0 @@ -""" -This file contains the main interface and the public API for multilspy. -The abstract class LanguageServer provides a factory method, creator that is -intended for creating instantiations of language specific clients. -The details of Language Specific configuration are not exposed to the user. -""" - -import asyncio -import dataclasses -import hashlib -import json -import logging -import os -import pathlib -import pickle -import re -import threading -from collections import defaultdict -from contextlib import asynccontextmanager, contextmanager -from copy import copy -from pathlib import Path, PurePath -import time -from typing import AsyncIterator, Dict, Iterator, List, Optional, Tuple, Union, cast - -import pathspec -import tqdm - -from . import multilspy_types -from .lsp_protocol_handler import lsp_types as LSPTypes -from .lsp_protocol_handler.lsp_constants import LSPConstants -from .lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink, SymbolKind -from .lsp_protocol_handler.server import ( - Error, - LanguageServerHandler, - ProcessLaunchInfo, - StringDict, -) -from .multilspy_config import Language, MultilspyConfig -from .multilspy_exceptions import MultilspyException -from .multilspy_logger import MultilspyLogger -from .multilspy_utils import FileUtils, PathUtils, TextUtils -from .type_helpers import ensure_all_methods_implemented -from .lsp_protocol_handler import lsp_types - -# Serena dependencies -# We will need to watch out for circular imports, but it's probably better to not -# move all generic util code from serena into multilspy. -# It does however make sense to integrate many text-related utils into the language server -# since it caches (in-memory) file contents, so we can avoid reading from disk. -# Moreover, the way we want to use the language server (for retrieving actual content), -# it makes sense to have more content-related utils directly in it. -from serena.text_utils import LineType, MatchedConsecutiveLines, TextLine, search_files - - - -GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, multilspy_types.UnifiedSymbolInformation] - -@dataclasses.dataclass(kw_only=True) -class ReferenceInSymbol: - """A symbol retrieved when requesting reference to a symbol, together with the location of the reference""" - symbol: multilspy_types.UnifiedSymbolInformation - line: int - character: int - -@dataclasses.dataclass -class LSPFileBuffer: - """ - This class is used to store the contents of an open LSP file in memory. - """ - - # uri of the file - uri: str - - # The contents of the file - contents: str - - # The version of the file - version: int - - # The language id of the file - language_id: str - - # reference count of the file - ref_count: int - - # --------------------------------- MODIFICATIONS BY MISCHA --------------------------------- - - content_hash: str = "" - - def __post_init__(self): - self.content_hash = hashlib.md5(self.contents.encode('utf-8')).hexdigest() - - -class LanguageServer: - """ - The LanguageServer class provides a language agnostic interface to the Language Server Protocol. - It is used to communicate with Language Servers of different programming languages. - """ - - # To be overridden and extended by subclasses - def is_ignored_dirname(self, dirname: str) -> bool: - """ - A language-specific condition for directories that should always be ignored. For example, venv - in Python and node_modules in JS/TS should be ignored always. - """ - return dirname.startswith('.') - - @classmethod - def create(cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str) -> "LanguageServer": - """ - Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language. - - If language is Java, then ensure that jdk-17.0.6 or higher is installed, `java` is in PATH, and JAVA_HOME is set to the installation directory. - If language is JS/TS, then ensure that node (v18.16.0 or higher) is installed and in PATH. - - :param repository_root_path: The root path of the repository. - :param config: The Multilspy configuration. - :param logger: The logger to use. - :return LanguageServer: A language specific LanguageServer instance. - """ - if config.code_language == Language.PYTHON: - from multilspy.language_servers.pyright_language_server.pyright_server import ( - PyrightServer, - ) - - return PyrightServer(config, logger, repository_root_path) - # It used to be jedi, but pyright is a bit faster, and also more actively maintained - # Keeping the previous code for reference - # from multilspy.language_servers.jedi_language_server.jedi_server import ( - # JediServer, - # ) - - # return JediServer(config, logger, repository_root_path) - elif config.code_language == Language.JAVA: - from multilspy.language_servers.eclipse_jdtls.eclipse_jdtls import ( - EclipseJDTLS, - ) - - return EclipseJDTLS(config, logger, repository_root_path) - elif config.code_language == Language.KOTLIN: - from multilspy.language_servers.kotlin_language_server.kotlin_language_server import ( - KotlinLanguageServer, - ) - - return KotlinLanguageServer(config, logger, repository_root_path) - elif config.code_language == Language.RUST: - from multilspy.language_servers.rust_analyzer.rust_analyzer import ( - RustAnalyzer, - ) - - return RustAnalyzer(config, logger, repository_root_path) - elif config.code_language == Language.CSHARP: - from multilspy.language_servers.omnisharp.omnisharp import OmniSharp - - return OmniSharp(config, logger, repository_root_path) - elif config.code_language in [Language.TYPESCRIPT, Language.JAVASCRIPT]: - from multilspy.language_servers.typescript_language_server.typescript_language_server import ( - TypeScriptLanguageServer, - ) - return TypeScriptLanguageServer(config, logger, repository_root_path) - elif config.code_language == Language.GO: - from multilspy.language_servers.gopls.gopls import Gopls - - return Gopls(config, logger, repository_root_path) - elif config.code_language == Language.RUBY: - from multilspy.language_servers.solargraph.solargraph import Solargraph - - return Solargraph(config, logger, repository_root_path) - elif config.code_language == Language.DART: - from multilspy.language_servers.dart_language_server.dart_language_server import DartLanguageServer - - return DartLanguageServer(config, logger, repository_root_path) - elif config.code_language == Language.CPP: - from multilspy.language_servers.clangd_language_server.clangd_language_server import ClangdLanguageServer - - return ClangdLanguageServer(config, logger, repository_root_path) - elif config.code_language == Language.PHP: - from multilspy.language_servers.intelephense.intelephense import Intelephense - - return Intelephense(config, logger, repository_root_path) - else: - logger.log(f"Language {config.code_language} is not supported", logging.ERROR) - raise MultilspyException(f"Language {config.code_language} is not supported") - - def __init__( - self, - config: MultilspyConfig, - logger: MultilspyLogger, - repository_root_path: str, - process_launch_info: ProcessLaunchInfo, - language_id: str, - ): - """ - Initializes a LanguageServer instance. - - 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. - """ - if type(self) == LanguageServer: - raise MultilspyException( - "LanguageServer is an abstract class and cannot be instantiated directly. Use LanguageServer.create method instead." - ) - - 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) - - self.language_id = language_id - self.open_file_buffers: Dict[str, LSPFileBuffer] = {} - self.language = Language(language_id) - - # load cache first to prevent any racing conditions due to asyncio stuff - self._document_symbols_cache: dict[str, Tuple[str, Tuple[List[multilspy_types.UnifiedSymbolInformation], List[multilspy_types.UnifiedSymbolInformation]]]] = {} - """Maps file paths to a tuple of (file_content_hash, result_of_request_document_symbols)""" - self._cache_lock = threading.Lock() - self._cache_has_changed: bool = False - self.load_cache() - - self.server_started = False - self.completions_available = asyncio.Event() - if config.trace_lsp_communication: - def logging_fn(source: str, target: str, msg: StringDict | str): - self.logger.log(f"LSP: {source} -> {target}: {str(msg)[:90]}...", self.logger.logger.level) - else: - logging_fn = None - - - # 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) - self.server = LanguageServerHandler( - process_launch_info, - logger=logging_fn, - start_independent_lsp_process=config.start_independent_lsp_process, - ) - - - # Set up the pathspec matcher for the ignored paths - # for all absolute paths in ignored_paths, convert them to relative paths - processed_patterns = [] - for pattern in set(config.ignored_paths): - # 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) - - # Create a pathspec matcher from the processed patterns - self._ignore_spec = pathspec.PathSpec.from_lines( - pathspec.patterns.GitWildMatchPattern, - processed_patterns - ) - - def get_ignore_spec(self) -> pathspec.PathSpec: - """Returns the pathspec matcher for the paths that were configured to be ignored through - the multilspy config. - - This is 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, - such as when searching for relevant non-language files in the project. - """ - return self._ignore_spec - - def is_ignored_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool: - """ - Determine if a path should be ignored based on file type - and ignore patterns. - - :param relative_path: Relative path to check - :param ignore_unsupported_files: whether files that are not supported source files should be ignored - - :return: True if the path should be ignored, False otherwise - """ - abs_path = os.path.join(self.repository_root_path, relative_path) - if not os.path.exists(abs_path): - raise FileNotFoundError(f"File {abs_path} not found, the ignore check cannot be performed") - - # Check file extension if it's a file - is_file = os.path.isfile(abs_path) - if is_file and ignore_unsupported_files: - fn_matcher = self.language.get_source_fn_matcher() - if not fn_matcher.is_relevant_filename(abs_path): - return True - - # Create normalized path for consistent handling - rel_path = Path(relative_path) - - # Check each part of the path against always fulfilled ignore conditions - dir_parts = rel_path.parts - if is_file: - dir_parts = dir_parts[:-1] - for part in dir_parts: - if not part: # Skip empty parts (e.g., from leading '/') - continue - if self.is_ignored_dirname(part): - return True - - # Use pathspec for gitignore-style pattern matching - # Normalize path separators for pathspec (it expects forward slashes) - normalized_path = str(rel_path).replace(os.path.sep, '/') - - # pathspec can't handle the matching of directories if they don't end with a slash! - # see https://github.com/cpburnz/python-pathspec/issues/89 - if os.path.isdir(os.path.join(self.repository_root_path, normalized_path)) and not normalized_path.endswith('/'): - normalized_path = normalized_path + '/' - - # Use the pathspec matcher to check if the path matches any ignore pattern - if self.get_ignore_spec().match_file(normalized_path): - return True - - return False - - async def _shutdown(self, timeout: float = 5.0): - """ - A robust shutdown process designed to terminate cleanly on all platforms, including Windows, - by explicitly closing all I/O pipes. - """ - if not self.server.is_running(): - self.logger.log("Server process not running, skipping shutdown.", logging.DEBUG) - return - - self.logger.log(f"Initiating final robust shutdown with a {timeout}s timeout...", logging.INFO) - process = self.server.process - reader_tasks = list(self.server.tasks.values()) - - # --- Main Shutdown Logic --- - try: - # Stage 1: Graceful Termination Request - # Send LSP shutdown and close stdin to signal no more input. - try: - await asyncio.wait_for(self.server.shutdown(), timeout=2.0) - if process.stdin and not process.stdin.is_closing(): - process.stdin.close() - except Exception: - pass # Ignore errors here, we are proceeding to terminate anyway. - - # Stage 2: Terminate and Concurrently Drain stdout/stderr - process.terminate() - - # Wait for the process to exit AND for the output pipes to be drained. - # The reader tasks will exit when they hit EOF. - await asyncio.wait_for( - asyncio.gather(process.wait(), *reader_tasks), - timeout=timeout - 2.0 - ) - self.logger.log("Process terminated and output pipes drained.", logging.INFO) - - except asyncio.TimeoutError: - # Stage 3: Forceful Kill - self.logger.log("Graceful termination failed. Forcefully killing process...", logging.WARNING) - if self.server.is_running(): - try: - process.kill() - # Wait for the killed process to be reaped by the OS. - await process.wait() - except Exception as e: - self.logger.log(f"Error during forceful kill: {e}", logging.ERROR) - - except Exception as e: - self.logger.log(f"An unexpected error occurred during shutdown logic: {e}", logging.ERROR) - - finally: - # === STAGE 4: EXPLICIT TASK & PIPE CLEANUP === - self.logger.log("Performing final task cancellation and pipe handle cleanup...", logging.DEBUG) - - # 1. Cancel any lingering reader tasks. - for task in reader_tasks: - if not task.done(): - task.cancel() - # Wait for cancellations to complete. - await asyncio.gather(*reader_tasks, return_exceptions=True) - self.server.tasks = {} - - # 2. Explicitly close each pipe to release OS handles. - for pipe in [process.stdin, process.stdout, process.stderr]: - if pipe and hasattr(pipe, 'is_closing') and not pipe.is_closing(): - try: - pipe.close() - except Exception: - pass - - # 3. Null out the process object in the handler. - self.server.process = None - self.logger.log("Shutdown sequence fully finished.", logging.DEBUG) - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["LanguageServer"]: - """ - Starts the Language Server and yields the LanguageServer instance. - """ - self.server_started = True - try: - yield self - finally: - self.server_started = False - await self._shutdown() - - @contextmanager - def open_file(self, relative_file_path: str) -> Iterator[LSPFileBuffer]: - """ - Open a file in the Language Server. This is required before making any requests to the Language Server. - - :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, - ) - raise MultilspyException("Language Server not started") - - absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) - uri = pathlib.Path(absolute_file_path).as_uri() - - if uri in self.open_file_buffers: - assert self.open_file_buffers[uri].uri == uri - assert self.open_file_buffers[uri].ref_count >= 1 - - self.open_file_buffers[uri].ref_count += 1 - yield self.open_file_buffers[uri] - self.open_file_buffers[uri].ref_count -= 1 - else: - contents = FileUtils.read_file(self.logger, absolute_file_path) - - version = 0 - self.open_file_buffers[uri] = LSPFileBuffer(uri, contents, version, self.language_id, 1) - - self.server.notify.did_open_text_document( - { - LSPConstants.TEXT_DOCUMENT: { - LSPConstants.URI: uri, - LSPConstants.LANGUAGE_ID: self.language_id, - LSPConstants.VERSION: 0, - LSPConstants.TEXT: contents, - } - } - ) - yield self.open_file_buffers[uri] - self.open_file_buffers[uri].ref_count -= 1 - - if self.open_file_buffers[uri].ref_count == 0: - self.server.notify.did_close_text_document( - { - LSPConstants.TEXT_DOCUMENT: { - LSPConstants.URI: uri, - } - } - ) - del self.open_file_buffers[uri] - - def insert_text_at_position( - self, relative_file_path: str, line: int, column: int, text_to_be_inserted: str - ) -> multilspy_types.Position: - """ - Insert text at the given line and column in the given file and return - the updated cursor position after inserting the text. - - :param relative_file_path: The relative path of the file to open. - :param line: The line number at which text should be inserted. - :param column: The column number at which text should be inserted. - :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, - ) - raise MultilspyException("Language Server not started") - - absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) - uri = pathlib.Path(absolute_file_path).as_uri() - - # Ensure the file is open - assert uri in self.open_file_buffers - - file_buffer = self.open_file_buffers[uri] - file_buffer.version += 1 - - new_contents, new_l, new_c = TextUtils.insert_text_at_position(file_buffer.contents, line, column, text_to_be_inserted) - file_buffer.contents = new_contents - self.server.notify.did_change_text_document( - { - LSPConstants.TEXT_DOCUMENT: { - LSPConstants.VERSION: file_buffer.version, - LSPConstants.URI: file_buffer.uri, - }, - LSPConstants.CONTENT_CHANGES: [ - { - LSPConstants.RANGE: { - "start": {"line": line, "character": column}, - "end": {"line": line, "character": column}, - }, - "text": text_to_be_inserted, - } - ], - } - ) - return multilspy_types.Position(line=new_l, character=new_c) - - def delete_text_between_positions( - self, - relative_file_path: str, - start: multilspy_types.Position, - end: multilspy_types.Position, - ) -> str: - """ - 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, - ) - raise MultilspyException("Language Server not started") - - absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) - uri = pathlib.Path(absolute_file_path).as_uri() - - # Ensure the file is open - assert uri in self.open_file_buffers - - file_buffer = self.open_file_buffers[uri] - file_buffer.version += 1 - new_contents, deleted_text = TextUtils.delete_text_between_positions(file_buffer.contents, start_line=start["line"], start_col=start["character"], end_line=end["line"], end_col=end["character"]) - file_buffer.contents = new_contents - self.server.notify.did_change_text_document( - { - LSPConstants.TEXT_DOCUMENT: { - LSPConstants.VERSION: file_buffer.version, - LSPConstants.URI: file_buffer.uri, - }, - LSPConstants.CONTENT_CHANGES: [{LSPConstants.RANGE: {"start": start, "end": end}, "text": ""}], - } - ) - return deleted_text - - async def _send_definition_request(self, definition_params: DefinitionParams) -> Union[Definition, List[LocationLink], None]: - return await self.server.send.definition(definition_params) - - async def request_definition( - self, relative_file_path: str, line: int, column: int - ) -> List[multilspy_types.Location]: - """ - Raise a [textDocument/definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) request to the Language Server - for the symbol at the given line and column in the given file. Wait for the response and return the result. - - :param relative_file_path: The relative path of the file that has the symbol for which definition should be looked up - :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 - """ - - if not self.server_started: - self.logger.log( - "find_function_definition called before Language Server started", - logging.ERROR, - ) - raise MultilspyException("Language Server not started") - - with self.open_file(relative_file_path): - # sending request to the language server and waiting for response - definition_params = cast(DefinitionParams, { - LSPConstants.TEXT_DOCUMENT: { - LSPConstants.URI: pathlib.Path( - str(PurePath(self.repository_root_path, relative_file_path)) - ).as_uri() - }, - LSPConstants.POSITION: { - LSPConstants.LINE: line, - LSPConstants.CHARACTER: column, - }, - }) - response = await self._send_definition_request(definition_params) - - ret: List[multilspy_types.Location] = [] - if isinstance(response, list): - # response is either of type Location[] or LocationLink[] - for item in response: - assert isinstance(item, dict) - if LSPConstants.URI in item and LSPConstants.RANGE in item: - new_item: multilspy_types.Location = {} - new_item.update(item) - new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) - new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) - ret.append(multilspy_types.Location(new_item)) - elif ( - LSPConstants.ORIGIN_SELECTION_RANGE in item - and LSPConstants.TARGET_URI in item - and LSPConstants.TARGET_RANGE in item - and LSPConstants.TARGET_SELECTION_RANGE in item - ): - new_item: multilspy_types.Location = {} - new_item["uri"] = item[LSPConstants.TARGET_URI] - new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) - new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) - new_item["range"] = item[LSPConstants.TARGET_SELECTION_RANGE] - ret.append(multilspy_types.Location(**new_item)) - else: - assert False, f"Unexpected response from Language Server: {item}" - elif isinstance(response, dict): - # response is of type Location - assert LSPConstants.URI in response - assert LSPConstants.RANGE in response - - new_item: multilspy_types.Location = {} - new_item.update(response) - new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) - new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) - ret.append(multilspy_types.Location(**new_item)) - 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, - ) - else: - assert False, f"Unexpected response from Language Server: {response}" - - return ret - - # Some LS cause problems with this, so the call is isolated from the rest to allow overriding in subclasses - async def _send_references_request(self, relative_file_path: str, line: int, column: int) -> List[lsp_types.Location] | None: - return await self.server.send.references( - { - "textDocument": {"uri": PathUtils.path_to_uri(os.path.join(self.repository_root_path, relative_file_path))}, - "position": {"line": line, "character": column}, - "context": {"includeDeclaration": False}, - } - ) - - async def request_references( - self, relative_file_path: str, line: int, column: int - ) -> List[multilspy_types.Location]: - """ - Raise a [textDocument/references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) request to the Language Server - to find references to the symbol at the given line and column in the given file. Wait for the response and return the result. - Filters out references located in ignored directories. - - :param relative_file_path: The relative path of the file that has the symbol for which references should be looked up - :param line: The line number of the symbol - :param column: The column number of the symbol - - :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, - ) - raise MultilspyException("Language Server not started") - - - with self.open_file(relative_file_path): - try: - response = await self._send_references_request(relative_file_path, line=line, column=column) - except Exception as e: - # Catch LSP internal error (-32603) and raise a more informative exception - if isinstance(e, Error) and getattr(e, 'code', None) == -32603: - raise RuntimeError( - f"LSP internal error (-32603) when requesting references for {relative_file_path}:{line}:{column}. " - "This often occurs when requesting references for a symbol not referenced in the expected way. " - ) from e - raise - if response is None: - return [] - - ret: List[multilspy_types.Location] = [] - assert isinstance(response, list), f"Unexpected response from Language Server (expected list, got {type(response)}): {response}" - for item in response: - assert isinstance(item, dict), f"Unexpected response from Language Server (expected dict, got {type(item)}): {item}" - assert LSPConstants.URI in item - assert LSPConstants.RANGE in item - - abs_path = PathUtils.uri_to_path(item[LSPConstants.URI]) - 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) - continue - - new_item: multilspy_types.Location = {} - new_item.update(item) - new_item["absolutePath"] = str(abs_path) - new_item["relativePath"] = str(rel_path) - ret.append(multilspy_types.Location(**new_item)) - - return ret - - async def request_references_with_content( - self, relative_file_path: str, line: int, column: int, context_lines_before: int = 0, context_lines_after: int = 0 - ) -> List[MatchedConsecutiveLines]: - """ - Like request_references, but returns the content of the lines containing the references, not just the locations. - - :param relative_file_path: The relative path of the file that has the symbol for which references should be looked up - :param line: The line number of the symbol - :param column: The column number of the symbol - :param context_lines_before: The number of lines to include in the context before the line containing the reference - :param context_lines_after: The number of lines to include in the context after the line containing the reference - - :return: A list of MatchedConsecutiveLines objects, one for each reference. - """ - references = await self.request_references(relative_file_path, line, column) - return [self.retrieve_content_around_line(ref["relativePath"], ref["range"]["start"]["line"], context_lines_before, context_lines_after) for ref in references] - - def retrieve_full_file_content(self, relative_file_path: str) -> str: - """ - Retrieve the full content of the given file. - """ - with self.open_file(relative_file_path) as file_data: - return file_data.contents - - def retrieve_content_around_line(self, relative_file_path: str, line: int, context_lines_before: int = 0, context_lines_after: int = 0) -> MatchedConsecutiveLines: - """ - Retrieve the content of the given file around the given line. - - :param relative_file_path: The relative path of the file to retrieve the content from - :param line: The line number to retrieve the content around - :param context_lines_before: The number of lines to retrieve before the given line - :param context_lines_after: The number of lines to retrieve after the given line - - :return MatchedConsecutiveLines: A container with the desired lines. - """ - with self.open_file(relative_file_path) as file_data: - file_contents = file_data.contents - return MatchedConsecutiveLines.from_file_contents(file_contents, line=line, context_lines_before=context_lines_before, context_lines_after=context_lines_after, source_file_path=relative_file_path) - - - async def request_completions( - self, relative_file_path: str, line: int, column: int, allow_incomplete: bool = False - ) -> List[multilspy_types.CompletionItem]: - """ - Raise a [textDocument/completion](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion) request to the Language Server - to find completions at the given line and column in the given file. Wait for the response and return the result. - - :param relative_file_path: The relative path of the file that has the symbol for which completions should be looked up - :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 - """ - 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() - ] - completion_params: LSPTypes.CompletionParams = { - "position": {"line": line, "character": column}, - "textDocument": {"uri": open_file_buffer.uri}, - "context": {"triggerKind": LSPTypes.CompletionTriggerKind.Invoked}, - } - response: Union[List[LSPTypes.CompletionItem], LSPTypes.CompletionList, None] = None - - num_retries = 0 - while response is None or (response["isIncomplete"] and num_retries < 30): - await self.completions_available.wait() - response: Union[ - List[LSPTypes.CompletionItem], LSPTypes.CompletionList, None - ] = await self.server.send.completion(completion_params) - if isinstance(response, list): - response = {"items": response, "isIncomplete": False} - num_retries += 1 - - # TODO: Understand how to appropriately handle `isIncomplete` - if response is None or (response["isIncomplete"] and not(allow_incomplete)): - return [] - - if "items" in response: - response = response["items"] - - response: List[LSPTypes.CompletionItem] = response - - # TODO: Handle the case when the completion is a keyword - items = [item for item in response if item["kind"] != LSPTypes.CompletionItemKind.Keyword] - - completions_list: List[multilspy_types.CompletionItem] = [] - - for item in items: - assert "insertText" in item or "textEdit" in item - assert "kind" in item - completion_item = {} - if "detail" in item: - completion_item["detail"] = item["detail"] - - if "label" in item: - completion_item["completionText"] = item["label"] - completion_item["kind"] = item["kind"] - elif "insertText" in item: - completion_item["completionText"] = item["insertText"] - completion_item["kind"] = item["kind"] - elif "textEdit" in item and "newText" in item["textEdit"]: - completion_item["completionText"] = item["textEdit"]["newText"] - completion_item["kind"] = item["kind"] - elif "textEdit" in item and "range" in item["textEdit"]: - new_dot_lineno, new_dot_colno = ( - completion_params["position"]["line"], - completion_params["position"]["character"], - ) - assert all( - ( - item["textEdit"]["range"]["start"]["line"] == new_dot_lineno, - item["textEdit"]["range"]["start"]["character"] == new_dot_colno, - item["textEdit"]["range"]["start"]["line"] == item["textEdit"]["range"]["end"]["line"], - item["textEdit"]["range"]["start"]["character"] - == item["textEdit"]["range"]["end"]["character"], - ) - ) - - completion_item["completionText"] = item["textEdit"]["newText"] - completion_item["kind"] = item["kind"] - elif "textEdit" in item and "insert" in item["textEdit"]: - assert False - else: - assert False - - completion_item = multilspy_types.CompletionItem(**completion_item) - completions_list.append(completion_item) - - return [ - json.loads(json_repr) - for json_repr in set([json.dumps(item, sort_keys=True) for item in completions_list]) - ] - - async def request_document_symbols(self, relative_file_path: str, include_body: bool = False) -> Tuple[List[multilspy_types.UnifiedSymbolInformation], List[multilspy_types.UnifiedSymbolInformation]]: - """ - Raise a [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol) request to the Language Server - to find symbols in the given file. Wait for the response and return the result. - - :param relative_file_path: The relative path of the file that has the symbols - :param include_body: whether to include the body of the symbols in the result. - :return: A list of symbols in the file, and a list of root symbols that represent the tree structure of the symbols. - All symbols will have a location, a children, and a parent attribute, - where the parent attribute is None for root symbols. - Note that this is slightly different from the call to request_full_symbol_tree, - where the parent attribute will be the file symbol which in turn may have a package symbol as parent. - If you need a symbol tree that contains file symbols as well, you should use `request_full_symbol_tree` instead. - """ - # TODO: it's kinda dumb to not use the cache if include_body is False after include_body was True once - # Should be fixed in the future, it's a small performance optimization - cache_key = f"{relative_file_path}-{include_body}" - with self.open_file(relative_file_path) as file_data: - with self._cache_lock: - file_hash_and_result = self._document_symbols_cache.get(cache_key) - if file_hash_and_result is not None: - file_hash, result = file_hash_and_result - if file_hash == file_data.content_hash: - self.logger.log(f"Returning cached document symbols for {relative_file_path}", logging.DEBUG) - return result - else: - self.logger.log(f"Content for {relative_file_path} has changed. Will overwrite in-memory cache", logging.DEBUG) - else: - self.logger.log(f"No cache hit for symbols with {include_body=} in {relative_file_path}", logging.DEBUG) - - self.logger.log(f"Requesting document symbols for {relative_file_path} from the Language Server", logging.DEBUG) - response = await self.server.send.document_symbol( - { - "textDocument": { - "uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri() - } - } - ) - self.logger.log(f"Received {len(response) if response is not None else None} document symbols for {relative_file_path} from the Language Server", logging.DEBUG) - - def turn_item_into_symbol_with_children(item: GenericDocumentSymbol): - item = cast(multilspy_types.UnifiedSymbolInformation, item) - absolute_path = os.path.join(self.repository_root_path, relative_file_path) - - # handle missing entries in location - if "location" not in item: - uri = pathlib.Path(absolute_path).as_uri() - assert "range" in item - tree_location = multilspy_types.Location( - uri=uri, - range=item['range'], - absolutePath=absolute_path, - relativePath=relative_file_path, - ) - item['location'] = tree_location - location = item["location"] - if "absolutePath" not in location: - location["absolutePath"] = absolute_path - if "relativePath" not in location: - location["relativePath"] = relative_file_path - if include_body: - item['body'] = self.retrieve_symbol_body(item) - # handle missing selectionRange - if "selectionRange" not in item: - if "range" in item: - item["selectionRange"] = item["range"] - else: - item["selectionRange"] = item["location"]["range"] - children = item.get(LSPConstants.CHILDREN, []) - for child in children: - child["parent"] = item - item[LSPConstants.CHILDREN] = children - - flat_all_symbol_list: List[multilspy_types.UnifiedSymbolInformation] = [] - assert isinstance(response, list), f"Unexpected response from Language Server: {response}" - root_nodes: List[multilspy_types.UnifiedSymbolInformation] = [] - for root_item in response: - if "range" not in root_item and "location" not in root_item: - if root_item["kind"] in [SymbolKind.File, SymbolKind.Module]: - ... - - # mutation is more convenient than creating a new dict, - # so we cast and rename the var after the mutating call to turn_item_into_symbol_with_children - # which turned and item into a "symbol" - turn_item_into_symbol_with_children(root_item) - root_symbol = cast(multilspy_types.UnifiedSymbolInformation, root_item) - root_symbol["parent"] = None - - root_nodes.append(root_symbol) - assert isinstance(root_symbol, dict) - assert LSPConstants.NAME in root_symbol - assert LSPConstants.KIND in root_symbol - - if LSPConstants.CHILDREN in root_symbol: - # TODO: l_tree should be a list of TreeRepr. Define the following function to return TreeRepr as well - - def visit_tree_nodes_and_build_tree_repr(node: GenericDocumentSymbol) -> List[multilspy_types.UnifiedSymbolInformation]: - node = cast(multilspy_types.UnifiedSymbolInformation, node) - l: List[multilspy_types.UnifiedSymbolInformation] = [] - turn_item_into_symbol_with_children(node) - assert LSPConstants.CHILDREN in node - children = node[LSPConstants.CHILDREN] - l.append(node) - for child in children: - l.extend(visit_tree_nodes_and_build_tree_repr(child)) - return l - - flat_all_symbol_list.extend(visit_tree_nodes_and_build_tree_repr(root_symbol)) - else: - flat_all_symbol_list.append(multilspy_types.UnifiedSymbolInformation(**root_symbol)) - - result = flat_all_symbol_list, root_nodes - self.logger.log(f"Caching document symbols for {relative_file_path}", logging.DEBUG) - with self._cache_lock: - self._document_symbols_cache[cache_key] = (file_data.content_hash, result) - self._cache_has_changed = True - return result - - async def request_full_symbol_tree(self, within_relative_path: str | None = None, include_body: bool = False) -> List[multilspy_types.UnifiedSymbolInformation]: - """ - Will go through all files in the project or within a relative path and build a tree of symbols. - Note: this may be slow the first time it is called, especially if `within_relative_path` is not used to restrict the search. - - For each file, a symbol of kind File (2) will be created. For directories, a symbol of kind Package (4) will be created. - All symbols will have a children attribute, thereby representing the tree structure of all symbols in the project - that are within the repository. - All symbols except the root packages will have a parent attribute. - Will ignore directories starting with '.', language-specific defaults - and user-configured directories (e.g. from .gitignore). - - :param within_relative_path: pass a relative path to only consider symbols within this path. - If a file is passed, only the symbols within this file will be considered. - If a directory is passed, all files within this directory will be considered. - :param include_body: whether to include the body of the symbols in the result. - - :return: A list of root symbols representing the top-level packages/modules in the project. - """ - - if within_relative_path is not None: - within_abs_path = os.path.join(self.repository_root_path, within_relative_path) - if not os.path.exists(within_abs_path): - raise FileNotFoundError(f"File or directory not found: {within_abs_path}") - if os.path.isfile(within_abs_path): - if self.is_ignored_path(within_relative_path): - self.logger.log(f"You passed a file explicitly, but it is ignored. This is probably an error. File: {within_relative_path}", logging.ERROR) - return [] - else: - _, root_nodes = await self.request_document_symbols(within_relative_path, include_body=include_body) - return root_nodes - - # Helper function to recursively process directories - async def process_directory(rel_dir_path: str) -> List[multilspy_types.UnifiedSymbolInformation]: - abs_dir_path = self.repository_root_path if rel_dir_path == "." else os.path.join(self.repository_root_path, rel_dir_path) - abs_dir_path = os.path.realpath(abs_dir_path) - - if self.is_ignored_path(str(Path(abs_dir_path).relative_to(self.repository_root_path))): - self.logger.log(f"Skipping directory: {rel_dir_path}\n(because it should be ignored)", logging.DEBUG) - return [] - - result = [] - try: - contained_dir_or_file_names = os.listdir(abs_dir_path) - except OSError: - return [] - - # Create package symbol for directory - package_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore - name=os.path.basename(abs_dir_path), - kind=multilspy_types.SymbolKind.Package, - location=multilspy_types.Location( - uri=str(pathlib.Path(abs_dir_path).as_uri()), - range={"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}, - absolutePath=str(abs_dir_path), - relativePath=str(Path(abs_dir_path).resolve().relative_to(self.repository_root_path)), - ), - children=[] - ) - result.append(package_symbol) - - for contained_dir_or_file_name in contained_dir_or_file_names: - contained_dir_or_file_abs_path = os.path.join(abs_dir_path, contained_dir_or_file_name) - contained_dir_or_file_rel_path = str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path)) - if self.is_ignored_path(contained_dir_or_file_rel_path): - self.logger.log(f"Skipping item: {contained_dir_or_file_rel_path}\n(because it should be ignored)", logging.DEBUG) - continue - - if os.path.isdir(contained_dir_or_file_abs_path): - child_symbols = await process_directory(contained_dir_or_file_rel_path) - package_symbol["children"].extend(child_symbols) - for child in child_symbols: - child["parent"] = package_symbol - - elif os.path.isfile(contained_dir_or_file_abs_path): - _, file_root_nodes = await self.request_document_symbols(contained_dir_or_file_rel_path, include_body=include_body) - - # Create file symbol, link with children - file_rel_path = str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path)) - with self.open_file(file_rel_path) as file_data: - fileRange = self._get_range_from_file_content(file_data.contents) - file_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore - name=os.path.splitext(contained_dir_or_file_name)[0], - kind=multilspy_types.SymbolKind.File, - range=fileRange, - selectionRange=fileRange, - location=multilspy_types.Location( - uri=str(pathlib.Path(contained_dir_or_file_abs_path).as_uri()), - range=fileRange, - absolutePath=str(contained_dir_or_file_abs_path), - relativePath=str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path)), - ), - children=file_root_nodes, - parent=package_symbol, - ) - for child in file_root_nodes: - child["parent"] = file_symbol - - # Link file symbol with package - package_symbol["children"].append(file_symbol) - - # TODO: Not sure if this is actually still needed given recent changes to relative path handling - def fix_relative_path(nodes: List[multilspy_types.UnifiedSymbolInformation]): - for node in nodes: - if "location" in node and "relativePath" in node["location"]: - path = Path(node["location"]["relativePath"]) - if path.is_absolute(): - try: - path = path.relative_to(self.repository_root_path) - node["location"]["relativePath"] = str(path) - except Exception: - pass - if "children" in node: - fix_relative_path(node["children"]) - - fix_relative_path(file_root_nodes) - - return result - - # Start from the root or the specified directory - start_rel_path = within_relative_path or "." - return await process_directory(start_rel_path) - - @staticmethod - def _get_range_from_file_content(file_content: str) -> multilspy_types.Range: - """ - Get the range for the given file. - """ - lines = file_content.split("\n") - end_line = len(lines) - end_column = len(lines[-1]) - return multilspy_types.Range( - start=multilspy_types.Position(line=0, character=0), - end=multilspy_types.Position(line=end_line, character=end_column) - ) - - async def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: - """ - An overview of the given directory. - - Maps relative paths of all contained files to info about top-level symbols in the file - (name, kind, line, column). - """ - symbol_tree = await self.request_full_symbol_tree(relative_dir_path) - # Initialize result dictionary - result: dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]] = defaultdict(list) - - # Helper function to process a symbol and its children - def process_symbol(symbol: multilspy_types.UnifiedSymbolInformation): - if symbol["kind"] == multilspy_types.SymbolKind.File: - # For file symbols, process their children (top-level symbols) - for child in symbol["children"]: - assert "location" in child - assert "selectionRange" in child - path = Path(child["location"]["absolutePath"]).resolve().relative_to(self.repository_root_path) - result[str(path)].append(( - child["name"], - child["kind"], - child["selectionRange"]["start"]["line"], - child["selectionRange"]["start"]["character"] - )) - # For package/directory symbols, process their children - for child in symbol["children"]: - process_symbol(child) - - # Process each root symbol - for root in symbol_tree: - process_symbol(root) - return result - - async def request_document_overview(self, relative_file_path: str) -> list[tuple[str, multilspy_types.SymbolKind, int, int]]: - """ - An overview of the given file. - Returns the list of tuples (name, kind, line, column) of all top-level symbols in the file. - """ - _, document_roots = await self.request_document_symbols(relative_file_path) - result = [] - for root in document_roots: - try: - result.append( - ( root["name"], - root["kind"], - root["selectionRange"]["start"]["line"], - root["selectionRange"]["start"]["character"],) - ) - except KeyError as e: - raise KeyError( - f"Could not process symbol of name {root.get('name', 'unknown')} in {relative_file_path=}" - ) from e - return result - - async def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: - """ - An overview of all symbols in the given file or directory. - - :param within_relative_path: the relative path to the file or directory to get the overview of. - :return: A mapping of all relative paths analyzed to lists of tuples (name, kind, line, column) of all top-level symbols in the corresponding file. - """ - abs_path = (Path(self.repository_root_path) / within_relative_path).resolve() - if not abs_path.exists(): - raise FileNotFoundError(f"File or directory not found: {abs_path}") - - if abs_path.is_file(): - symbols_overview = await self.request_document_overview(within_relative_path) - return {within_relative_path: symbols_overview} - else: - return await self.request_dir_overview(within_relative_path) - - async def request_hover(self, relative_file_path: str, line: int, column: int) -> Union[multilspy_types.Hover, None]: - """ - Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server - to find the hover information at the given line and column in the given file. Wait for the response and return the result. - - :param relative_file_path: The relative path of the file that has the hover information - :param line: The line number of the symbol - :param column: The column number of the symbol - - :return None - """ - with self.open_file(relative_file_path): - response = await self.server.send.hover( - { - "textDocument": { - "uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri() - }, - "position": { - "line": line, - "character": column, - }, - } - ) - - if response is None: - return None - - assert isinstance(response, dict) - - return multilspy_types.Hover(**response) - - # ----------------------------- FROM HERE ON MODIFICATIONS BY MISCHA -------------------- - - def retrieve_symbol_body(self, symbol: multilspy_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation) -> str: - """ - Load the body of the given symbol. If the body is already contained in the symbol, just return it. - """ - existing_body = symbol.get("body", None) - if existing_body: - return existing_body - - assert "location" in symbol - symbol_start_line = symbol["location"]["range"]["start"]["line"] - symbol_end_line = symbol["location"]["range"]["end"]["line"] - assert "relativePath" in symbol["location"] - symbol_file = self.retrieve_full_file_content(symbol["location"]["relativePath"]) - symbol_lines = symbol_file.split("\n") - symbol_body = "\n".join(symbol_lines[symbol_start_line:symbol_end_line+1]) - - # remove leading indentation - symbol_start_column = symbol["location"]["range"]["start"]["character"] - symbol_body = symbol_body[symbol_start_column:] - return symbol_body - - async def request_parsed_files(self) -> list[str]: - """Retrieves relative paths of all files analyzed by the Language Server.""" - if not self.server_started: - self.logger.log( - "request_parsed_files called before Language Server started", - logging.ERROR, - ) - raise MultilspyException("Language Server not started") - rel_file_paths = [] - for root, dirs, files in os.walk(self.repository_root_path): - dirs[:] = [d for d in dirs if not self.is_ignored_path(os.path.join(root, d))] - for file in files: - rel_file_path = os.path.relpath(os.path.join(root, file), start=self.repository_root_path) - if not self.is_ignored_path(rel_file_path): - rel_file_paths.append(rel_file_path) - return rel_file_paths - - async def search_files_for_pattern( - self, - pattern: re.Pattern | str, - context_lines_before: int = 0, - context_lines_after: int = 0, - paths_include_glob: str | None = None, - paths_exclude_glob: str | None = None, - ) -> list[MatchedConsecutiveLines]: - """ - Search for a pattern across all files analyzed by the Language Server. - - :param pattern: Regular expression pattern to search for, either as a compiled Pattern or string - :param context_lines_before: Number of lines of context to include before each match - :param context_lines_after: Number of lines of context to include after each match - :param paths_include_glob: Glob pattern to filter which files to include in the search - :param paths_exclude_glob: Glob pattern to filter which files to exclude from the search. Takes precedence over paths_include_glob. - :return: List of matched consecutive lines with context - """ - if isinstance(pattern, str): - pattern = re.compile(pattern) - - relative_file_paths = await self.request_parsed_files() - return search_files( - relative_file_paths, - pattern, - file_reader=self.retrieve_full_file_content, - context_lines_before=context_lines_before, - context_lines_after=context_lines_after, - paths_include_glob=paths_include_glob, - paths_exclude_glob=paths_exclude_glob - ) - - async def request_referencing_symbols( - self, - relative_file_path: str, - line: int, - column: int, - include_imports: bool = True, - include_self: bool = False, - include_body: bool = False, - include_file_symbols: bool = False, - ) -> List[ReferenceInSymbol]: - """ - Finds all symbols that reference the symbol at the given location. - This is similar to request_references but filters to only include symbols - (functions, methods, classes, etc.) that reference the target symbol. - - :param relative_file_path: The relative path to the file. - :param line: The 0-indexed line number. - :param column: The 0-indexed column number. - :param include_imports: whether to also include imports as references. - Unfortunately, the LSP does not have an import type, so the references corresponding to imports - will not be easily distinguishable from definitions. - :param include_self: whether to include the references that is the "input symbol" itself. - Only has an effect if the relative_file_path, line and column point to a symbol, for example a definition. - :param include_body: whether to include the body of the symbols in the result. - :param include_file_symbols: whether to include references that are file symbols. This - is often a fallback mechanism for when the reference cannot be resolved to a symbol. - :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, - ) - raise MultilspyException("Language Server not started") - - # First, get all references to the symbol - references = await self.request_references(relative_file_path, line, column) - if not references: - return [] - - # For each reference, find the containing symbol - result = [] - incoming_symbol = None - for ref in references: - ref_path = ref["relativePath"] - ref_line = ref["range"]["start"]["line"] - ref_col = ref["range"]["start"]["character"] - - with self.open_file(ref_path) as file_data: - # Get the containing symbol for this reference - containing_symbol = await self.request_containing_symbol( - ref_path, ref_line, ref_col, include_body=include_body - ) - if containing_symbol is None: - # TODO: HORRIBLE HACK! I don't know how to do it better for now... - # THIS IS BOUND TO BREAK IN MANY CASES! IT IS ALSO SPECIFIC TO PYTHON! - # Background: - # When a variable is used to change something, like - # - # instance = MyClass() - # instance.status = "new status" - # - # we can't find the containing symbol for the reference to `status` - # since there is no container on the line of the reference - # The hack is to try to find a variable symbol in the containing module - # by using the text of the reference to find the variable name (In a very heuristic way) - # and then look for a symbol with that name and kind Variable - ref_text = file_data.contents.split("\n")[ref_line] - if "." in ref_text: - containing_symbol_name = ref_text.split(".")[0] - all_symbols, _ = await self.request_document_symbols(ref_path) - for symbol in all_symbols: - if symbol["name"] == containing_symbol_name and symbol["kind"] == multilspy_types.SymbolKind.Variable: - containing_symbol = copy(symbol) - containing_symbol["location"] = ref - containing_symbol["range"] = ref["range"] - break - - # 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 - ) - fileRange = self._get_range_from_file_content(file_data.contents) - location = multilspy_types.Location( - uri=str(pathlib.Path(os.path.join(self.repository_root_path, ref_path)).as_uri()), - range=fileRange, - absolutePath=str(os.path.join(self.repository_root_path, ref_path)), - relativePath=ref_path, - ) - name = os.path.splitext(os.path.basename(ref_path))[0] - - if include_body: - body = self.retrieve_full_file_content(ref_path) - else: - body = "" - - containing_symbol = multilspy_types.UnifiedSymbolInformation( - kind=multilspy_types.SymbolKind.File, - range=fileRange, - selectionRange=fileRange, - location=location, - name=name, - children=[], - body=body, - ) - if containing_symbol is None or not include_file_symbols and containing_symbol["kind"] == multilspy_types.SymbolKind.File: - continue - - assert "location" in containing_symbol - assert "selectionRange" in containing_symbol - - # Checking for self-reference - if ( - containing_symbol["location"]["relativePath"] == relative_file_path - and containing_symbol["selectionRange"]["start"]["line"] == ref_line - and containing_symbol["selectionRange"]["start"]["character"] == ref_col - ): - incoming_symbol = containing_symbol - if include_self: - result.append(ReferenceInSymbol(symbol=containing_symbol, line=ref_line, character=ref_col)) - continue - else: - self.logger.log(f"Found self-reference for {incoming_symbol['name']}, skipping it since {include_self=}", logging.DEBUG) - continue - - # checking whether reference is an import - # This is neither really safe nor elegant, but if we don't do it, - # there is no way to distinguish between definitions and imports as import is not a symbol-type - # and we get the type referenced symbol resulting from imports... - if (not include_imports \ - and incoming_symbol is not None \ - and containing_symbol["name"] == incoming_symbol["name"] \ - and containing_symbol["kind"] == incoming_symbol["kind"] \ - ): - self.logger.log( - f"Found import of referenced symbol {incoming_symbol['name']}" - f"in {containing_symbol['location']['relativePath']}, skipping", - logging.DEBUG - ) - continue - - result.append(ReferenceInSymbol(symbol=containing_symbol, line=ref_line, character=ref_col)) - - return result - - async def request_containing_symbol( - self, - relative_file_path: str, - line: int, - column: Optional[int] = None, - strict: bool = False, - include_body: bool = False, - ) -> multilspy_types.UnifiedSymbolInformation | None: - """ - Finds the first symbol containing the position for the given file. - For Python, container symbols are considered to be those with kinds corresponding to - functions, methods, or classes (typically: Function (12), Method (6), Class (5)). - - The method operates as follows: - - Request the document symbols for the file. - - Filter symbols to those that start at or before the given line. - - From these, first look for symbols whose range contains the (line, column). - - If one or more symbols contain the position, return the one with the greatest starting position - (i.e. the innermost container). - - If none (strictly) contain the position, return the symbol with the greatest starting position - among those above the given line. - - If no container candidates are found, return None. - - :param relative_file_path: The relative path to the Python file. - :param line: The 0-indexed line number. - :param column: The 0-indexed column (also called character). If not passed, the lookup will be based - only on the line. - :param strict: If True, the position must be strictly within the range of the symbol. - Setting to True is useful for example for finding the parent of a symbol, as with strict=False, - and the line pointing to a symbol itself, the containing symbol will be the symbol itself - (and not the parent). - :param include_body: Whether to include the body of the symbol in the result. - :return: The container symbol (if found) or None. - """ - # checking if the line is empty, unfortunately ugly and duplicating code, but I don't want to refactor - with self.open_file(relative_file_path): - absolute_file_path = str( - PurePath(self.repository_root_path, relative_file_path) - ) - content = FileUtils.read_file(self.logger, absolute_file_path) - 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, - ) - return None - - symbols, _ = await self.request_document_symbols(relative_file_path) - - # make jedi and pyright api compatible - # the former has no location, the later has no range - # we will just always add location of the desired format to all symbols - for symbol in symbols: - if "location" not in symbol: - range = symbol["range"] - location = multilspy_types.Location( - uri=f"file:/{absolute_file_path}", - range=range, - absolutePath=absolute_file_path, - relativePath=relative_file_path, - ) - symbol["location"] = location - else: - location = symbol["location"] - assert "range" in location - location["absolutePath"] = absolute_file_path - location["relativePath"] = relative_file_path - location["uri"] = Path(absolute_file_path).as_uri() - - # Allowed container kinds, currently only for Python - container_symbol_kinds = { - multilspy_types.SymbolKind.Method, - multilspy_types.SymbolKind.Function, - multilspy_types.SymbolKind.Class - } - - def is_position_in_range(line: int, range_d: multilspy_types.Range) -> bool: - start = range_d["start"] - end = range_d["end"] - - column_condition = True - if strict: - line_condition = end["line"] >= line > start["line"] - if column is not None and line == start["line"]: - column_condition = column > start["character"] - else: - line_condition = end["line"] >= line >= start["line"] - if column is not None and line == start["line"]: - column_condition = column >= start["character"] - return line_condition and column_condition - - # Only consider containers that are not one-liners (otherwise we may get imports) - candidate_containers = [ - s for s in symbols if s["kind"] in container_symbol_kinds and s["location"]["range"]["start"]["line"] != s["location"]["range"]["end"]["line"] - ] - var_containers = [ - s for s in symbols if s["kind"] == multilspy_types.SymbolKind.Variable - ] - candidate_containers.extend(var_containers) - - if not candidate_containers: - return None - - # From the candidates, find those whose range contains the given position. - containing_symbols = [] - for symbol in candidate_containers: - s_range = symbol["location"]["range"] - if not is_position_in_range(line, s_range): - continue - containing_symbols.append(symbol) - - if containing_symbols: - # Return the one with the greatest starting position (i.e. the innermost container). - containing_symbol = max(containing_symbols, key=lambda s: s["location"]["range"]["start"]["line"]) - if include_body: - containing_symbol["body"] = self.retrieve_symbol_body(containing_symbol) - return containing_symbol - else: - return None - - async def request_container_of_symbol(self, symbol: multilspy_types.UnifiedSymbolInformation, include_body: bool = False) -> multilspy_types.UnifiedSymbolInformation | None: - """ - Finds the container of the given symbol if there is one. If the parent attribute is present, the parent is returned - without further searching. - - :param symbol: The symbol to find the container of. - :param include_body: whether to include the body of the symbol in the result. - :return: The container of the given symbol or None if no container is found. - """ - if "parent" in symbol: - return symbol["parent"] - assert "location" in symbol, f"Symbol {symbol} has no location and no parent attribute" - return await self.request_containing_symbol( - symbol["location"]["relativePath"], - symbol["location"]["range"]["start"]["line"], - symbol["location"]["range"]["start"]["character"], - strict=True, - include_body=include_body, - ) - - async def request_defining_symbol( - self, - relative_file_path: str, - line: int, - column: int, - include_body: bool = False, - ) -> Optional[multilspy_types.UnifiedSymbolInformation]: - """ - Finds the symbol that defines the symbol at the given location. - - This method first finds the definition of the symbol at the given position, - then retrieves the full symbol information for that definition. - - :param relative_file_path: The relative path to the file. - :param line: The 0-indexed line number. - :param column: The 0-indexed column number. - :param include_body: whether to include the body of the symbol in the result. - :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, - ) - raise MultilspyException("Language Server not started") - - # Get the definition location(s) - definitions = await self.request_definition(relative_file_path, line, column) - if not definitions: - return None - - # Use the first definition location - definition = definitions[0] - def_path = definition["relativePath"] - def_line = definition["range"]["start"]["line"] - def_col = definition["range"]["start"]["character"] - - # Find the symbol at or containing this location - defining_symbol = await self.request_containing_symbol( - def_path, def_line, def_col, strict=False, include_body=include_body - ) - - return defining_symbol - - @property - def cache_path(self) -> Path: - """ - The path to the cache file for the document symbols. - """ - return Path(self.repository_root_path) / ".serena" / "cache" / self.language_id / "document_symbols_cache_v20-05-25.pkl" - - async def index_repository(self, progress_bar: bool = True, save_after_n_files: int = 10) -> None: - """Will go through the entire repository and "index" all files, meaning save their symbols to the cache. - - :param progress_bar: Whether to show a progress bar while indexing the repository. - :param save_after_n_files: How many files to process before saving a checkpoint of the cache. - """ - parsed_files = await self.request_parsed_files() - files_processed = 0 - pbar = tqdm.tqdm(parsed_files, disable=not progress_bar) - for relative_file_path in pbar: - pbar.set_description(f"Indexing ({os.path.basename(relative_file_path)})") - await self.request_document_symbols(relative_file_path, include_body=False) - await self.request_document_symbols(relative_file_path, include_body=True) - files_processed += 1 - if files_processed % save_after_n_files == 0: - self.save_cache() - self.save_cache() - - def save_cache(self): - with self._cache_lock: - if not self._cache_has_changed: - self.logger.log("No changes to document symbols cache, skipping save", logging.DEBUG) - return - - self.logger.log(f"Saving updated document symbols cache to {self.cache_path}", logging.INFO) - self.cache_path.parent.mkdir(parents=True, exist_ok=True) - try: - with open(self.cache_path, "wb") as f: - pickle.dump(self._document_symbols_cache, f) - self._cache_has_changed = False - except Exception as e: - self.logger.log( - f"Failed to save document symbols cache to {self.cache_path}: {e}. " - "Note: this may have resulted in a corrupted cache file.", - logging.ERROR, - ) - - def load_cache(self): - if not self.cache_path.exists(): - return - - with self._cache_lock: - self.logger.log(f"Loading document symbols cache from {self.cache_path}", logging.INFO) - try: - with open(self.cache_path, "rb") as f: - self._document_symbols_cache = pickle.load(f) - self.logger.log(f"Loaded {len(self._document_symbols_cache)} document symbols from cache.", logging.INFO) - except Exception as e: - # cache often becomes corrupt, so just skip loading it - self.logger.log( - f"Failed to load document symbols cache from {self.cache_path}: {e}. Possible cause: the cache file is corrupted. " - "Check for any errors related to saving the cache in the logs.", - logging.ERROR, - ) - - - async def request_workspace_symbol(self, query: str) -> Union[List[multilspy_types.UnifiedSymbolInformation], None]: - """ - Raise a [workspace/symbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) request to the Language Server - to find symbols across the whole workspace. Wait for the response and return the result. - - :param query: The query string to filter symbols by - - :return: A list of matching symbols - """ - response = await self.server.send.workspace_symbol({"query": query}) - if response is None: - return None - - assert isinstance(response, list) - - ret: List[multilspy_types.UnifiedSymbolInformation] = [] - for item in response: - assert isinstance(item, dict) - - assert LSPConstants.NAME in item - assert LSPConstants.KIND in item - assert LSPConstants.LOCATION in item - - ret.append(multilspy_types.UnifiedSymbolInformation(**item)) - - return ret - -@ensure_all_methods_implemented(LanguageServer) -class SyncLanguageServer: - """ - The SyncLanguageServer class provides a language agnostic interface to the Language Server Protocol. - It is used to communicate with Language Servers of different programming languages. - """ - - def __init__(self, language_server: LanguageServer, timeout: Optional[float] = None): - """ - :param language_server: the async language server being wrapped - :param timeout: the timeout, in seconds, to use for requests to the language server. - """ - self.language_server = language_server - self.loop = None - self.loop_thread = None - self.timeout = timeout - - self._server_context = None - - self._shutdown_lock = threading.Lock() - self._is_shutting_down = False - - @property - def cache_path(self) -> Path: - """ - The path to the cache file for the document symbols. - """ - return self.language_server.cache_path - - @classmethod - def create( - cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str, - timeout: Optional[float] = None - ) -> "SyncLanguageServer": - """ - Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language. - - If language is Java, then ensure that jdk-17.0.6 or higher is installed, `java` is in PATH, and JAVA_HOME is set to the installation directory. - - :param repository_root_path: The root path of the repository (must be absolute). - :param config: The Multilspy configuration. - :param logger: The logger to use. - :param timeout: the timeout, in seconds, to use for requests; if None, use no timeout - - :return SyncLanguageServer: A language specific LanguageServer instance. - """ - return SyncLanguageServer(LanguageServer.create(config, logger, repository_root_path), timeout=timeout) - - @property - def repository_root_path(self) -> str: - return self.language_server.repository_root_path - - @contextmanager - def open_file(self, relative_file_path: str) -> Iterator[LSPFileBuffer]: - """ - Open a file in the Language Server. This is required before making any requests to the Language Server. - - :param relative_file_path: The relative path of the file to open. - """ - with self.language_server.open_file(relative_file_path) as file_buffer: - yield file_buffer - - def insert_text_at_position( - self, relative_file_path: str, line: int, column: int, text_to_be_inserted: str - ) -> multilspy_types.Position: - """ - Insert text at the given line and column in the given file and return - the updated cursor position after inserting the text. - - :param relative_file_path: The relative path of the file to open. - :param line: The line number at which text should be inserted. - :param column: The column number at which text should be inserted. - :param text_to_be_inserted: The text to insert. - """ - return self.language_server.insert_text_at_position(relative_file_path, line, column, text_to_be_inserted) - - def delete_text_between_positions( - self, - relative_file_path: str, - start: multilspy_types.Position, - end: multilspy_types.Position, - ) -> str: - """ - Delete text between the given start and end positions in the given file and return the deleted text. - """ - return self.language_server.delete_text_between_positions(relative_file_path, start, end) - - @contextmanager - def start_server(self) -> Iterator["SyncLanguageServer"]: - """ - Starts the language server process and connects to it. - - :return: None - """ - self.start() - yield self - self.language_server.logger.log("SyncLS Startup: exiting LS context", logging.DEBUG) - self.stop() - - def request_definition(self, file_path: str, line: int, column: int) -> List[multilspy_types.Location]: - """ - Raise a [textDocument/definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) request to the Language Server - for the symbol at the given line and column in the given file. Wait for the response and return the result. - - :param file_path: The path of the file relative to the repository root. - :param line: The line number (zero-indexed). - :param column: The column number (zero-indexed). - - :return: A list of Locations defining the symbol. - """ - if not self.is_running(): - raise RuntimeError("Language server is not running. Cannot make requests to a stopped language server.") - - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_definition(file_path, line, column), self.loop - ).result(timeout=self.timeout) - return result - - def request_references(self, file_path: str, line: int, column: int) -> List[multilspy_types.Location]: - """ - Raises a [textDocument/references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) request to the Language Server - for the symbol at the given line and column in the given file. Wait for the response and return the result. - - :param file_path: The path of the file relative to the repository root. - :param line: The line number (zero-indexed). - :param column: The column number (zero-indexed). - - :return: A list of Locations referencing the symbol. - """ - if not self.is_running(): - raise RuntimeError("Language server is not running. Cannot make requests to a stopped language server.") - - try: - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_references(file_path, line, column), self.loop - ).result(timeout=self.timeout) - except Exception as e: - from multilspy.lsp_protocol_handler.server import Error - if isinstance(e, Error) and getattr(e, 'code', None) == -32603: - raise RuntimeError( - f"LSP internal error (-32603) when requesting references for {file_path}:{line}:{column}. " - "This often occurs when requesting references for a symbol not referenced in the expected way. " - ) from e - raise - return result - - - def request_references_with_content( - self, relative_file_path: str, line: int, column: int, context_lines_before: int = 0, context_lines_after: int = 0 - ) -> List[MatchedConsecutiveLines]: - """ - Like request_references, but returns the content of the lines containing the references, not just the locations. - - :param relative_file_path: The relative path of the file that has the symbol for which references should be looked up - :param line: The line number of the symbol - :param column: The column number of the symbol - :param context_lines_before: The number of lines to include in the context before the line containing the reference - :param context_lines_after: The number of lines to include in the context after the line containing the reference - - :return: A list of MatchedConsecutiveLines objects, one for each reference. - """ - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_references_with_content(relative_file_path, line, column, context_lines_before, context_lines_after), self.loop - ).result() - return result - - def request_completions( - self, relative_file_path: str, line: int, column: int, allow_incomplete: bool = False - ) -> List[multilspy_types.CompletionItem]: - """ - Raise a [textDocument/completion](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion) request to the Language Server - to find completions at the given line and column in the given file. Wait for the response and return the result. - - :param relative_file_path: The relative path of the file that has the symbol for which completions should be looked up - :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 - """ - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_completions(relative_file_path, line, column, allow_incomplete), - self.loop, - ).result(timeout=self.timeout) - return result - - def request_document_symbols(self, relative_file_path: str, include_body: bool = False) -> Tuple[List[multilspy_types.UnifiedSymbolInformation], List[multilspy_types.UnifiedSymbolInformation]]: - """ - Raise a [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol) request to the Language Server - to find symbols in the given file. Wait for the response and return the result. - - :param relative_file_path: The relative path of the file that has the symbols - :param include_body: whether to include the body of the symbols in the result. - :return: A list of symbols in the file, and a list of root symbols that represent the tree structure of the symbols. Each symbol in hierarchy starting from the roots has a children attribute. - """ - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_document_symbols(relative_file_path, include_body), self.loop - ).result() - return result - - def request_full_symbol_tree(self, within_relative_path: str | None = None, include_body: bool = False) -> List[multilspy_types.UnifiedSymbolInformation]: - """ - Will go through all files in the project and build a tree of symbols. Note: this may be slow the first time it is called. - - For each file, a symbol of kind Module (3) will be created. For directories, a symbol of kind Package (4) will be created. - All symbols will have a children attribute, thereby representing the tree structure of all symbols in the project - that are within the repository. - Will ignore directories starting with '.', language-specific defaults - and user-configured directories (e.g. from .gitignore). - - :param within_relative_path: pass a relative path to only consider symbols within this path. - If a file is passed, only the symbols within this file will be considered. - If a directory is passed, all files within this directory will be considered. - If None, the entire codebase will be considered. - :param include_body: whether to include the body of the symbols in the result. - - :return: A list of root symbols representing the top-level packages/modules in the project. - """ - if not self.is_running(): - raise RuntimeError("Language server is not running. Cannot make requests to a stopped language server.") - - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_full_symbol_tree(within_relative_path, include_body), self.loop - ).result(timeout=self.timeout) - return result - - def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: - """ - An overview of the given directory. - - Maps relative paths of all contained files to info about top-level symbols in the file - (name, kind, line, column). - """ - if not self.is_running(): - raise RuntimeError("Language server is not running. Cannot make requests to a stopped language server.") - - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_dir_overview(relative_dir_path), self.loop - ).result(timeout=self.timeout) - return result - - def request_document_overview(self, relative_file_path: str) -> list[tuple[str, multilspy_types.SymbolKind, int, int]]: - """ - An overview of the given file. - - Returns the list of tuples (name, kind, line, column) of all top-level symbols in the file. - """ - assert self.loop - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_document_overview(relative_file_path), self.loop - ).result(timeout=self.timeout) - return result - - def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: - """ - An overview of all symbols in the given file or directory. - - :param within_relative_path: the relative path to the file or directory to get the overview of. - :return: A mapping of all relative paths analyzed to lists of tuples (name, kind, line, column) of all top-level symbols in the corresponding file. - """ - assert self.loop - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_overview(within_relative_path), self.loop - ).result() - return result - - def request_hover(self, relative_file_path: str, line: int, column: int) -> Union[multilspy_types.Hover, None]: - """ - Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server - to find the hover information at the given line and column in the given file. Wait for the response and return the result. - - :param relative_file_path: The relative path of the file that has the hover information - :param line: The line number of the symbol - :param column: The column number of the symbol - - :return None - """ - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_hover(relative_file_path, line, column), self.loop - ).result(timeout=self.timeout) - return result - - def request_workspace_symbol(self, query: str) -> Union[List[multilspy_types.UnifiedSymbolInformation], None]: - """ - Raise a [workspace/symbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) request to the Language Server - to find symbols across the whole workspace. Wait for the response and return the result. - - :param query: The query string to filter symbols by - - :return Union[List[multilspy_types.UnifiedSymbolInformation], None]: A list of matching symbols - """ - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_workspace_symbol(query), self.loop - ).result(timeout=self.timeout) - return result - - # ----------------------------- FROM HERE ON MODIFICATIONS BY MISCHA -------------------- - - def retrieve_symbol_body(self, symbol: multilspy_types.UnifiedSymbolInformation) -> str: - """ - Load the body of the given symbol. If the body is already contained in the symbol, just return it. - - :param symbol: The symbol to retrieve the body of. - :return: The body of the symbol. - """ - return self.language_server.retrieve_symbol_body(symbol) - - def request_parsed_files(self) -> list[str]: - """Retrieves relative paths of all files analyzed by the Language Server.""" - assert self.loop - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_parsed_files(), self.loop - ).result() - return result - - def request_referencing_symbols( - self, relative_file_path: str, line: int, column: int, - include_imports: bool = True, include_self: bool = False, - include_body: bool = False, - include_file_symbols: bool = False, - ) -> List[ReferenceInSymbol]: - """ - Finds all symbols that reference the symbol at the given location. - This is similar to request_references but filters to only include symbols - (functions, methods, classes, etc.) that reference the target symbol. - - :param relative_file_path: The relative path to the file. - :param line: The 0-indexed line number. - :param column: The 0-indexed column number. - :param include_imports: whether to also include imports as references. - Unfortunately, the LSP does not have an import type, so the references corresponding to imports - will not be easily distinguishable from definitions. - :param include_self: whether to include the references that is the "input symbol" itself. - Only has an effect if the relative_file_path, line and column point to a symbol, for example a definition. - :param include_body: whether to include the body of the symbols in the result. - :param include_file_symbols: whether to include references that are file symbols. This - is often a fallback mechanism for when the reference cannot be resolved to a symbol. - :return: List of objects containing the symbol and the location of the reference. - """ - assert self.loop - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_referencing_symbols( - relative_file_path, - line, - column, - include_imports=include_imports, - include_self=include_self, - include_body=include_body, - include_file_symbols=include_file_symbols, - ), - self.loop - ).result(timeout=self.timeout) - return result - - def request_containing_symbol( - self, relative_file_path: str, line: int, - column: Optional[int] = None, strict: bool = False, - include_body: bool = False, - ) -> multilspy_types.UnifiedSymbolInformation | None: - """ - Finds the first symbol containing the position for the given file. - For Python, container symbols are considered to be those with kinds corresponding to - functions, methods, or classes (typically: Function (12), Method (6), Class (5)). - - The method operates as follows: - - Request the document symbols for the file. - - Filter symbols to those that start at or before the given line. - - From these, first look for symbols whose range contains the (line, column). - - If one or more symbols contain the position, return the one with the greatest starting position - (i.e. the innermost container). - - If none (strictly) contain the position, return the symbol with the greatest starting position - among those above the given line. - - If no container candidates are found, return None. - - :param relative_file_path: The relative path to the Python file. - :param line: The 0-indexed line number. - :param column: The 0-indexed column (also called character). If not passed, the lookup will be based - only on the line. - :param strict: If True, the position must be strictly within the range of the symbol. - Setting to true is useful for example for finding the parent of a symbol, as with strict=False, - and the line pointing to a symbol itself, the containing symbol will be the symbol itself - (and not the parent). - :param include_body: whether to include the body of the symbol in the result. - :return: The container symbol (if found) or None. - """ - assert self.loop - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_containing_symbol(relative_file_path, line, column=column, strict=strict, include_body=include_body), self.loop - ).result(timeout=self.timeout) - return result - - def request_container_of_symbol(self, symbol: multilspy_types.UnifiedSymbolInformation, include_body: bool = False) -> multilspy_types.UnifiedSymbolInformation | None: - """ - Finds the container of the given symbol if there is one. - - :param symbol: The symbol to find the container of. - :param include_body: whether to include the body of the symbol in the result. - """ - assert self.loop - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_container_of_symbol(symbol, include_body=include_body), self.loop - ).result(timeout=self.timeout) - return result - - def request_defining_symbol( - self, relative_file_path: str, line: int, column: int, - include_body: bool = False, - ) -> Optional[multilspy_types.UnifiedSymbolInformation]: - """ - Finds the symbol that defines the symbol at the given location. - - This method first finds the definition of the symbol at the given position, - then retrieves the full symbol information for that definition. - - :param relative_file_path: The relative path to the file. - :param line: The 0-indexed line number. - :param column: The 0-indexed column number. - :param include_body: whether to include the body of the symbol in the result. - :return: The symbol information for the definition, or None if not found. - """ - assert self.loop - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_defining_symbol(relative_file_path, line, column, include_body=include_body), self.loop - ).result(timeout=self.timeout) - return result - - def retrieve_full_file_content(self, relative_file_path: str) -> str: - """ - Retrieve the full content of the given file. - """ - return self.language_server.retrieve_full_file_content(relative_file_path) - - def retrieve_content_around_line(self, relative_file_path: str, line: int, context_lines_before: int = 0, context_lines_after: int = 0) -> MatchedConsecutiveLines: - """ - Retrieve the content of the given file around the given line. - - :param relative_file_path: The relative path of the file to retrieve the content from - :param line: The line number to retrieve the content around - :param context_lines_before: The number of lines to retrieve before the given line - :param context_lines_after: The number of lines to retrieve after the given line - :return MatchedConsecutiveLines: A container with the desired lines. - """ - return self.language_server.retrieve_content_around_line(relative_file_path, line, context_lines_before, context_lines_after) - - def search_files_for_pattern( - self, - pattern: re.Pattern | str, - context_lines_before: int = 0, - context_lines_after: int = 0, - paths_include_glob: str | None = None, - paths_exclude_glob: str | None = None, - ) -> list[MatchedConsecutiveLines]: - """ - Search for a pattern across all files analyzed by the Language Server. - - :param pattern: Regular expression pattern to search for, either as a compiled Pattern or string - :param context_lines_before: Number of lines of context to include before each match - :param context_lines_after: Number of lines of context to include after each match - :param paths_include_glob: Glob pattern to filter which files to include in the search - :param paths_exclude_glob: Glob pattern to filter which files to exclude from the search. Takes precedence over paths_include_glob. - :return: List of matched consecutive lines with context - """ - assert self.loop - result = asyncio.run_coroutine_threadsafe( - self.language_server.search_files_for_pattern(pattern, context_lines_before, context_lines_after, paths_include_glob, paths_exclude_glob), self.loop - ).result(timeout=self.timeout) - return result - - def index_repository(self, progress_bar: bool = True, timeout: float | None = None, save_after_n_files: int = 10) -> None: - """ - Will go through the entire repository and "index" all files, meaning save their symbols to the cache. - - :param progress_bar: Whether to show a progress bar while indexing the repository. - :param save_after_n_files: How many files to process before saving a checkpoint of the cache. - :param timeout: since this operation can take a long time, the default timeout (the attribute of the LS instance) is not used. - If you want to provide a timeout for indexing, you can pass it here explicitly. - :return: - """ - assert self.loop - asyncio.run_coroutine_threadsafe( - self.language_server.index_repository(progress_bar=progress_bar, save_after_n_files=save_after_n_files), self.loop - ).result(timeout=timeout) - - def start(self) -> "SyncLanguageServer": - """ - Starts the language server process and connects to it. Call shutdown when ready. - - :return: self for method chaining - """ - self.language_server.logger.log(f"Starting language server with language {self.language_server.language} for {self.language_server.repository_root_path}", logging.INFO) - self.language_server.logger.log("Creating new event loop", logging.DEBUG) - self.loop = asyncio.new_event_loop() - self.language_server.logger.log("Creating new thread for event loop", logging.DEBUG) - self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True) - self.loop_thread.start() - self.language_server.logger.log("Starting server (async) context", logging.DEBUG) - self._server_context = self.language_server.start_server() - self.language_server.logger.log("Entering server context", logging.DEBUG) - asyncio.run_coroutine_threadsafe(self._server_context.__aenter__(), loop=self.loop).result(timeout=self.timeout) - return self - - def is_running(self) -> bool: - """ - Check if the language server is running. - """ - return self.loop is not None and self.loop_thread is not None and self.loop_thread.is_alive() - - async def _shutdown_and_stop_loop(self): - """A coroutine that performs the full shutdown and then stops the event loop.""" - try: - if self._server_context: - await self._server_context.__aexit__(None, None, None) - except Exception as e: - self.language_server.logger.log(f"Exception during async shutdown: {e}", logging.ERROR) - finally: - # Clean up tasks but DON'T stop the loop from here - # Let the main thread handle loop stopping to avoid coroutine deadlocks - if self.loop and self.loop.is_running(): - current_task = asyncio.current_task(self.loop) - pending_tasks = [task for task in asyncio.all_tasks(self.loop) - if not task.done() and task is not current_task] - - if pending_tasks: - self.language_server.logger.log(f"Cancelling {len(pending_tasks)} pending tasks", logging.DEBUG) - for task in pending_tasks: - try: - task.cancel() - except Exception: - pass - - self.language_server.logger.log("Async shutdown tasks completed, returning to main thread", logging.DEBUG) - # Note: NOT calling loop.stop() here - let main thread do it - - def stop(self, shutdown_timeout: float = 2.0) -> None: - """ - Stops the language server and robustly cleans up all associated resources, - including the asyncio event loop, to prevent hangs on process exit. - """ - # 1. Use a lock and flag to make the shutdown call thread-safe and idempotent, - # ensuring the shutdown logic runs only once. - with self._shutdown_lock: - if self._is_shutting_down: - self.language_server.logger.log("Already shutting down or stopped, skipping", logging.DEBUG) - return - self._is_shutting_down = True - - if not self.is_running(): - self.language_server.logger.log("Language server is not running, skipping", logging.DEBUG) - return - - self.language_server.logger.log("Initiating server shutdown...", logging.INFO) - self.save_cache() - - # Detect platform to choose shutdown strategy - is_windows = os.name == "nt" - - if not is_windows: - # 2. Graceful shutdown for Linux/Unix - try proper async cleanup first - self.language_server.logger.log("Starting graceful shutdown", logging.INFO) - shutdown_future = None - if self.loop and self.loop.is_running(): - shutdown_future = asyncio.run_coroutine_threadsafe(self._shutdown_and_stop_loop(), self.loop) - - # 3. Wait for the background thread to exit - if self.loop_thread: - self.language_server.logger.log("Waiting for event loop thread to exit", logging.INFO) - self.loop_thread.join(timeout=shutdown_timeout) - if self.loop_thread.is_alive(): - self.language_server.logger.log("Event loop thread did not terminate within timeout", logging.WARNING) - - # 4. Wait for the shutdown coroutine to complete if it was scheduled - if shutdown_future: - self.language_server.logger.log("Waiting for async shutdown to complete", logging.INFO) - try: - shutdown_future.result(timeout=shutdown_timeout) - self.language_server.logger.log("Graceful async shutdown completed", logging.DEBUG) - except Exception as e: - self.language_server.logger.log(f"Async shutdown failed: {e}", logging.WARNING) - - # 5. Close the loop properly after graceful shutdown - if self.loop and not self.loop.is_closed(): - self.language_server.logger.log("Closing event loop", logging.INFO) - try: - self.loop.close() - self.language_server.logger.log("Event loop closed successfully", logging.DEBUG) - except Exception as e: - self.language_server.logger.log(f"Event loop close failed: {e}", logging.WARNING) - - else: - # WINDOWS NUCLEAR OPTION: Skip graceful shutdown - go straight to nuclear - # Windows IocpProactor has too many issues with proper cleanup - self.language_server.logger.log("Windows detected - using nuclear shutdown to prevent zombies", logging.WARNING) - - # Force stop the loop immediately without waiting for async cleanup - if self.loop and self.loop.is_running(): - try: - self.loop.call_soon_threadsafe(lambda: setattr(self.loop, '_stopping', True)) - self.loop.call_soon_threadsafe(self.loop.stop) - except Exception: - pass - - # Force mark loop as closed IMMEDIATELY to prevent zombies - try: - if hasattr(self.loop, '_closed'): - self.loop._closed = True - if hasattr(self.loop, '_stopping'): - self.loop._stopping = True - if hasattr(self.loop, '_ready'): - self.loop._ready.clear() - except Exception: - pass - - # Give thread minimal time to exit, then abandon it - if self.loop_thread: - self.loop_thread.join(timeout=1.0) # Only 1 second on Windows - if self.loop_thread.is_alive(): - self.language_server.logger.log("Thread stuck - abandoning to prevent GC deadlock", logging.WARNING) - - # 6. Null out everything immediately - same for both platforms - self.loop = None - self.loop_thread = None - self._server_context = None - self._is_shutting_down = False - - shutdown_type = "Nuclear" if is_windows else "Graceful" - self.language_server.logger.log(f"{shutdown_type} shutdown complete - all references cleared", logging.INFO) - - def save_cache(self): - """ - Save the cache to a file. - """ - self.language_server.save_cache() - - def load_cache(self): - """ - Load the cache from a file. - """ - self.language_server.load_cache() - - def is_ignored_dirname(self, dirname: str) -> bool: - """ - A language-specific condition for directories that should be ignored always. For example, venv - in Python and node_modules in JS/TS should be ignored always. - """ - return self.language_server.is_ignored_dirname(dirname) - - def is_ignored_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool: - """ - Whether the given path should be ignored. - """ - return self.language_server.is_ignored_path(relative_path, ignore_unsupported_files=ignore_unsupported_files) - - def get_ignore_spec(self) -> pathspec.PathSpec: - """Returns the pathspec matcher for the paths that were configured to be ignored through - the multilspy config. - - This is 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, - such as when searching for relevant non-language files in the project. - """ - return self.language_server.get_ignore_spec() diff --git a/src/multilspy/language_servers/clangd_language_server/clangd_language_server.py b/src/multilspy/language_servers/clangd_language_server/clangd_language_server.py deleted file mode 100644 index e28aa2c0..00000000 --- a/src/multilspy/language_servers/clangd_language_server/clangd_language_server.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++. -""" - -import asyncio -import json -import logging -import os -import stat -import pathlib -from contextlib import asynccontextmanager -from typing import AsyncIterator - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils - - -class ClangdLanguageServer(LanguageServer): - """ - Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++. - As the project gets bigger in size, building index will take time. Try running clangd multiple times to ensure index is built properly. - Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example. - """ - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - 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) - super().__init__( - config, - logger, - repository_root_path, - ProcessLaunchInfo(cmd=clangd_executable_path, cwd=repository_root_path), - "cpp", - ) - self.server_ready = asyncio.Event() - - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: - """ - Setup runtime dependencies for ClangdLanguageServer. - """ - platform_id = PlatformUtils.get_platform_id() - - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f: - d = json.load(f) - del d["_description"] - - assert platform_id.value in [ - "linux-x64", - "win-x64", - "osx-arm64", - ], "Unsupported platform: " + platform_id.value - - runtime_dependencies = d["runtimeDependencies"] - runtime_dependencies = [ - dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value - ] - assert len(runtime_dependencies) == 1 - # Select dependency matching the current platform - dependency = next((dep for dep in runtime_dependencies if dep["platformId"] == platform_id.value), None) - if dependency is None: - raise RuntimeError(f"No runtime dependency found for platform {platform_id.value}") - - clangd_ls_dir = os.path.join(os.path.dirname(__file__), "static", "clangd") - clangd_executable_path = os.path.join(clangd_ls_dir, "clangd_19.1.2", "bin", dependency["binaryName"]) - if not os.path.exists(clangd_executable_path): - clangd_url = dependency["url"] - logger.log(f"Clangd executable not found at {clangd_executable_path}. Downloading from {clangd_url}", logging.INFO) - os.makedirs(clangd_ls_dir, exist_ok=True) - if dependency["archiveType"] == "zip": - FileUtils.download_and_extract_archive( - logger, clangd_url, clangd_ls_dir, dependency["archiveType"] - ) - else: - raise RuntimeError(f"Unsupported archive type: {dependency['archiveType']}") - if not os.path.exists(clangd_executable_path): - raise FileNotFoundError( - f"Clangd executable not found at {clangd_executable_path}.\n" - "Make sure you have installed clangd. See https://clangd.llvm.org/installation" - ) - os.chmod(clangd_executable_path, stat.S_IEXEC) - - return clangd_executable_path - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the clangd Language Server. - """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r") as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["ClangdLanguageServer"]: - """ - Starts the Clangd Language Server, waits for the server to be ready and yields the LanguageServer instance. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and ready to serve requests - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown - """ - async def register_capability_handler(params): - assert "registrations" in params - for registration in params["registrations"]: - if registration["method"] == "workspace/executeCommand": - self.initialize_searcher_command_available.set() - self.resolve_main_method_available.set() - return - - async def lang_status_handler(params): - # TODO: Should we wait for - # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} - # Before proceeding? - if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": - self.service_ready_event.set() - - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def check_experimental_status(params): - if params["quiescent"] == True: - self.server_ready.set() - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - self.server.on_request("client/registerCapability", register_capability_handler) - self.server.on_notification("language/status", lang_status_handler) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - self.server.on_notification("experimental/serverStatus", check_experimental_status) - - async with super().start_server(): - self.logger.log("Starting Clangd server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 - assert "completionProvider" in init_response["capabilities"] - assert init_response["capabilities"]["completionProvider"] == { - "triggerCharacters": ['.', '<', '>', ':', '"', '/', '*'], - "resolveProvider": False, - } - - self.server.notify.initialized({}) - - self.completions_available.set() - # set ready flag - self.server_ready.set() - await self.server_ready.wait() - - yield self diff --git a/src/multilspy/language_servers/clangd_language_server/initialize_params.json b/src/multilspy/language_servers/clangd_language_server/initialize_params.json deleted file mode 100644 index 4330560e..00000000 --- a/src/multilspy/language_servers/clangd_language_server/initialize_params.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "locale": "en", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": { - "textDocument": { - "synchronization": { - "didSave": true, - "dynamicRegistration": true - }, - "completion": { - "dynamicRegistration": true, - "completionItem": { - "snippetSupport": true - } - }, - "definition": { - "dynamicRegistration": true - } - }, - "workspace": { - "workspaceFolders": true, - "didChangeConfiguration": { - "dynamicRegistration": true - } - } - }, - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/clangd_language_server/runtime_dependencies.json b/src/multilspy/language_servers/clangd_language_server/runtime_dependencies.json deleted file mode 100644 index 53f5b7f3..00000000 --- a/src/multilspy/language_servers/clangd_language_server/runtime_dependencies.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "_description": "Used to download the runtime dependencies for running Clangd.", - "runtimeDependencies": [ - { - "id": "Clangd", - "description": "Clangd for Linux (x64)", - "url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-linux-19.1.2.zip", - "platformId": "linux-x64", - "archiveType": "zip", - "binaryName": "clangd" - }, - { - "id": "Clangd", - "description": "Clangd for Windows (x64)", - "url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-windows-19.1.2.zip", - "platformId": "windows-x64", - "archiveType": "zip", - "binaryName": "clangd.exe" - }, - { - "id": "Clangd", - "description": "Clangd for macOS (Arm64)", - "url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip", - "platformId": "osx-arm64", - "archiveType": "zip", - "binaryName": "clangd" - } - ] -} diff --git a/src/multilspy/language_servers/dart_language_server/dart_language_server.py b/src/multilspy/language_servers/dart_language_server/dart_language_server.py deleted file mode 100644 index cd8ea518..00000000 --- a/src/multilspy/language_servers/dart_language_server/dart_language_server.py +++ /dev/null @@ -1,143 +0,0 @@ -from contextlib import asynccontextmanager -import logging -import os -import pathlib -import shutil -import stat -from typing import AsyncIterator -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -import json -from multilspy.multilspy_utils import FileUtils, PlatformUtils - - -class DartLanguageServer(LanguageServer): - """ - Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart. - """ - - def __init__(self, config, logger, repository_root_path): - """ - Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. - """ - - executable_path = self.setup_runtime_dependencies(logger) - super().__init__( - config, - logger, - repository_root_path, - ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path), - "dart", - ) - - def setup_runtime_dependencies(self, logger: "MultilspyLogger") -> str: - platform_id = PlatformUtils.get_platform_id() - - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f: - d = json.load(f) - del d["_description"] - - runtime_dependencies = d["runtimeDependencies"] - runtime_dependencies = [ - dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value - ] - - assert len(runtime_dependencies) == 1 - dependency = runtime_dependencies[0] - - dart_ls_dir = os.path.join(os.path.dirname(__file__), "static", "dart-language-server") - dart_executable_path = os.path.join(dart_ls_dir, dependency["binaryName"]) - - if not os.path.exists(dart_ls_dir): - os.makedirs(dart_ls_dir) - FileUtils.download_and_extract_archive( - logger, dependency["url"], dart_ls_dir, dependency["archiveType"] - ) - - - assert os.path.exists(dart_executable_path) - os.chmod(dart_executable_path, stat.S_IEXEC) - - return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2" - - - def _get_initialize_params(self, repository_absolute_path: str): - """ - Returns the initialize params for the Dart Language Server. - """ - with open( - os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r" - ) as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path( - repository_absolute_path - ).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["DartLanguageServer"]: - """ - Start the language server and yield when the server is ready. - """ - - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def check_experimental_status(params): - pass - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - self.server.on_request("client/registerCapability", do_nothing) - self.server.on_notification("language/status", do_nothing) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request( - "workspace/executeClientCommand", execute_client_command_handler - ) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - self.server.on_notification( - "experimental/serverStatus", check_experimental_status - ) - - async with super().start_server(): - self.logger.log( - "Starting dart-language-server server process", logging.INFO - ) - await 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, - ) - init_response = await self.server.send_request( - "initialize", initialize_params - ) - self.logger.log( - f"Received initialize response from dart-language-server: {init_response}", - logging.INFO, - ) - - self.server.notify.initialized({}) - - yield self diff --git a/src/multilspy/language_servers/dart_language_server/initialize_params.json b/src/multilspy/language_servers/dart_language_server/initialize_params.json deleted file mode 100644 index e90e408a..00000000 --- a/src/multilspy/language_servers/dart_language_server/initialize_params.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_description": "This file contains the initialization parameters for the Dart Language Server.", - "processId": "$processId", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": {}, - "initializationOptions": { - "onlyAnalyzeProjectsWithOpenFiles": false, - "suggestFromUnimportedLibraries": true, - "closingLabels": false, - "outline": false, - "flutterOutline": false, - "allowOpenUri": false - }, - "trace": "verbose", - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] - -} \ No newline at end of file diff --git a/src/multilspy/language_servers/dart_language_server/runtime_dependencies.json b/src/multilspy/language_servers/dart_language_server/runtime_dependencies.json deleted file mode 100644 index 32e49299..00000000 --- a/src/multilspy/language_servers/dart_language_server/runtime_dependencies.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "_description": "Used to download the runtime dependencies for running Dart Language Server, downloaded from https://dart.dev/get-dart/archive", - "runtimeDependencies": [ - { - "id": "DartLanguageServer", - "description": "Dart Language Server for Linux (x64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-linux-x64-release.zip", - "platformId": "linux-x64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart" - }, - { - "id": "DartLanguageServer", - "description": "Dart Language Server for Windows (x64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-x64-release.zip", - "platformId": "win-x64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart.exe" - }, - { - "id": "DartLanguageServer", - "description": "Dart Language Server for Windows (arm64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-arm64-release.zip", - "platformId": "win-arm64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart.exe" - }, - { - "id": "DartLanguageServer", - "description": "Dart Language Server for macOS (x64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-x64-release.zip", - "platformId": "osx-x64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart" - }, - { - "id": "DartLanguageServer", - "description": "Dart Language Server for macOS (arm64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-arm64-release.zip", - "platformId": "osx-arm64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py b/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py deleted file mode 100644 index a88dc1b6..00000000 --- a/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py +++ /dev/null @@ -1,422 +0,0 @@ -""" -Provides Java specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Java. -""" - -import asyncio -import dataclasses -import json -import logging -import os -import pathlib -import shutil -import stat -import uuid -from contextlib import asynccontextmanager -from typing import AsyncIterator - -from overrides import override - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_settings import MultilspySettings -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils -from pathlib import PurePath - - -@dataclasses.dataclass -class RuntimeDependencyPaths: - """ - Stores the paths to the runtime dependencies of EclipseJDTLS - """ - - gradle_path: str - lombok_jar_path: str - jre_path: str - jre_home_path: str - jdtls_launcher_jar_path: str - jdtls_readonly_config_path: str - intellicode_jar_path: str - intellisense_members_path: str - - -class EclipseJDTLS(LanguageServer): - """ - The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class - """ - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - 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) - self.runtime_dependency_paths = runtime_dependency_paths - - # ws_dir is the workspace directory for the EclipseJDTLS server - ws_dir = str( - PurePath( - MultilspySettings.get_language_server_directory(), - "EclipseJDTLS", - "workspaces", - uuid.uuid4().hex, - ) - ) - - # shared_cache_location is the global cache used by Eclipse JDTLS across all workspaces - shared_cache_location = str( - PurePath(MultilspySettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex") - ) - - jre_path = self.runtime_dependency_paths.jre_path - lombok_jar_path = self.runtime_dependency_paths.lombok_jar_path - - jdtls_launcher_jar = self.runtime_dependency_paths.jdtls_launcher_jar_path - - os.makedirs(ws_dir, exist_ok=True) - - data_dir = str(PurePath(ws_dir, "data_dir")) - jdtls_config_path = str(PurePath(ws_dir, "config_path")) - - jdtls_readonly_config_path = self.runtime_dependency_paths.jdtls_readonly_config_path - - if not os.path.exists(jdtls_config_path): - shutil.copytree(jdtls_readonly_config_path, jdtls_config_path) - - for static_path in [ - jre_path, - lombok_jar_path, - jdtls_launcher_jar, - jdtls_config_path, - jdtls_readonly_config_path, - ]: - assert os.path.exists(static_path), static_path - - # TODO: Add "self.runtime_dependency_paths.jre_home_path"/bin to $PATH as well - proc_env = {"syntaxserver": "false", "JAVA_HOME": self.runtime_dependency_paths.jre_home_path} - proc_cwd = repository_root_path - cmd = " ".join( - [ - jre_path, - "--add-modules=ALL-SYSTEM", - "--add-opens", - "java.base/java.util=ALL-UNNAMED", - "--add-opens", - "java.base/java.lang=ALL-UNNAMED", - "--add-opens", - "java.base/sun.nio.fs=ALL-UNNAMED", - "-Declipse.application=org.eclipse.jdt.ls.core.id1", - "-Dosgi.bundles.defaultStartLevel=4", - "-Declipse.product=org.eclipse.jdt.ls.core.product", - "-Djava.import.generatesMetadataFilesAtProjectRoot=false", - "-Dfile.encoding=utf8", - "-noverify", - "-XX:+UseParallelGC", - "-XX:GCTimeRatio=4", - "-XX:AdaptiveSizePolicyWeight=90", - "-Dsun.zip.disableMemoryMapping=true", - "-Djava.lsp.joinOnCompletion=true", - "-Xmx3G", - "-Xms100m", - "-Xlog:disable", - "-Dlog.level=ALL", - f'"-javaagent:{lombok_jar_path}"', - f'"-Djdt.core.sharedIndexLocation={shared_cache_location}"', - "-jar", - f'"{jdtls_launcher_jar}"', - "-configuration", - f'"{jdtls_config_path}"', - "-data", - f'"{data_dir}"', - ] - ) - - self.service_ready_event = asyncio.Event() - self.intellicode_enable_command_available = asyncio.Event() - self.initialize_searcher_command_available = asyncio.Event() - - super().__init__(config, logger, repository_root_path, ProcessLaunchInfo(cmd, proc_env, proc_cwd), "java") - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - # Ignore common Java build directories from different build tools: - # - Maven: target - # - Gradle: build, .gradle - # - Eclipse: bin, .settings - # - IntelliJ IDEA: out, .idea - # - General: classes, dist, lib - return super().is_ignored_dirname(dirname) or dirname in [ - "target", # Maven - "build", # Gradle - "bin", # Eclipse - "out", # IntelliJ IDEA - "classes", # General - "dist", # General - "lib" # General - ] - - def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> RuntimeDependencyPaths: - """ - Setup runtime dependencies for EclipseJDTLS. - """ - platformId = PlatformUtils.get_platform_id() - - with open(str(PurePath(os.path.dirname(__file__), "runtime_dependencies.json")), "r", encoding="utf-8") as f: - runtimeDependencies = json.load(f) - del runtimeDependencies["_description"] - - os.makedirs(str(PurePath(os.path.abspath(os.path.dirname(__file__)), "static")), exist_ok=True) - - # assert platformId.value in [ - # "linux-x64", - # "win-x64", - # ], "Only linux-x64 platform is supported for in multilspy at the moment" - - gradle_path = str( - PurePath( - os.path.abspath(os.path.dirname(__file__)), - "static/gradle-7.3.3", - ) - ) - - if not os.path.exists(gradle_path): - FileUtils.download_and_extract_archive( - logger, - runtimeDependencies["gradle"]["platform-agnostic"]["url"], - str(PurePath(gradle_path).parent), - runtimeDependencies["gradle"]["platform-agnostic"]["archiveType"], - ) - - assert os.path.exists(gradle_path) - - dependency = runtimeDependencies["vscode-java"][platformId.value] - vscode_java_path = str( - PurePath(os.path.abspath(os.path.dirname(__file__)), "static", dependency["relative_extraction_path"]) - ) - os.makedirs(vscode_java_path, exist_ok=True) - jre_home_path = str(PurePath(vscode_java_path, dependency["jre_home_path"])) - jre_path = str(PurePath(vscode_java_path, dependency["jre_path"])) - lombok_jar_path = str(PurePath(vscode_java_path, dependency["lombok_jar_path"])) - jdtls_launcher_jar_path = str(PurePath(vscode_java_path, dependency["jdtls_launcher_jar_path"])) - jdtls_readonly_config_path = str(PurePath(vscode_java_path, dependency["jdtls_readonly_config_path"])) - if not all( - [ - os.path.exists(vscode_java_path), - os.path.exists(jre_home_path), - os.path.exists(jre_path), - os.path.exists(lombok_jar_path), - os.path.exists(jdtls_launcher_jar_path), - os.path.exists(jdtls_readonly_config_path), - ] - ): - FileUtils.download_and_extract_archive( - logger, dependency["url"], vscode_java_path, dependency["archiveType"] - ) - - os.chmod(jre_path, stat.S_IEXEC) - - assert os.path.exists(vscode_java_path) - assert os.path.exists(jre_home_path) - assert os.path.exists(jre_path) - assert os.path.exists(lombok_jar_path) - assert os.path.exists(jdtls_launcher_jar_path) - assert os.path.exists(jdtls_readonly_config_path) - - dependency = runtimeDependencies["intellicode"]["platform-agnostic"] - intellicode_directory_path = str( - PurePath(os.path.abspath(os.path.dirname(__file__)), "static", dependency["relative_extraction_path"]) - ) - os.makedirs(intellicode_directory_path, exist_ok=True) - intellicode_jar_path = str(PurePath(intellicode_directory_path, dependency["intellicode_jar_path"])) - intellisense_members_path = str(PurePath(intellicode_directory_path, dependency["intellisense_members_path"])) - if not all( - [ - os.path.exists(intellicode_directory_path), - os.path.exists(intellicode_jar_path), - os.path.exists(intellisense_members_path), - ] - ): - FileUtils.download_and_extract_archive( - logger, dependency["url"], intellicode_directory_path, dependency["archiveType"] - ) - - assert os.path.exists(intellicode_directory_path) - assert os.path.exists(intellicode_jar_path) - assert os.path.exists(intellisense_members_path) - - return RuntimeDependencyPaths( - gradle_path=gradle_path, - lombok_jar_path=lombok_jar_path, - jre_path=jre_path, - jre_home_path=jre_home_path, - jdtls_launcher_jar_path=jdtls_launcher_jar_path, - jdtls_readonly_config_path=jdtls_readonly_config_path, - intellicode_jar_path=intellicode_jar_path, - intellisense_members_path=intellisense_members_path, - ) - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize parameters for the EclipseJDTLS server. - """ - # Look into https://github.com/eclipse/eclipse.jdt.ls/blob/master/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java to understand all the options available - with open(str(PurePath(os.path.dirname(__file__), "initialize_params.json")), "r", encoding="utf-8") as f: - d: InitializeParams = json.load(f) - - del d["_description"] - - if not os.path.isabs(repository_absolute_path): - repository_absolute_path = os.path.abspath(repository_absolute_path) - - assert d["processId"] == "os.getpid()" - d["processId"] = os.getpid() - - assert d["rootPath"] == "repository_absolute_path" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "pathlib.Path(repository_absolute_path).as_uri()" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["initializationOptions"]["workspaceFolders"] == "[pathlib.Path(repository_absolute_path).as_uri()]" - d["initializationOptions"]["workspaceFolders"] = [pathlib.Path(repository_absolute_path).as_uri()] - - assert ( - d["workspaceFolders"] - == '[\n {\n "uri": pathlib.Path(repository_absolute_path).as_uri(),\n "name": os.path.basename(repository_absolute_path),\n }\n ]' - ) - d["workspaceFolders"] = [ - { - "uri": pathlib.Path(repository_absolute_path).as_uri(), - "name": os.path.basename(repository_absolute_path), - } - ] - - assert d["initializationOptions"]["bundles"] == ["intellicode-core.jar"] - bundles = [self.runtime_dependency_paths.intellicode_jar_path] - d["initializationOptions"]["bundles"] = bundles - - assert d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] == [ - {"name": "JavaSE-21", "path": "static/vscode-java/extension/jre/21.0.7-linux-x86_64", "default": True} - ] - d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [ - {"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True} - ] - - for runtime in d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]: - assert "name" in runtime - assert "path" in runtime - assert os.path.exists( - runtime["path"] - ), f"Runtime required for eclipse_jdtls at path {runtime['path']} does not exist" - - assert d["initializationOptions"]["settings"]["java"]["import"]["gradle"]["home"] == "abs(static/gradle-7.3.3)" - d["initializationOptions"]["settings"]["java"]["import"]["gradle"][ - "home" - ] = self.runtime_dependency_paths.gradle_path - - d["initializationOptions"]["settings"]["java"]["import"]["gradle"]["java"][ - "home" - ] = self.runtime_dependency_paths.jre_path - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["EclipseJDTLS"]: - """ - Starts the Eclipse JDTLS Language Server, waits for the server to be ready and yields the LanguageServer instance. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and ready to serve requests - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown - ``` - """ - - async def register_capability_handler(params): - assert "registrations" in params - for registration in params["registrations"]: - if registration["method"] == "textDocument/completion": - assert registration["registerOptions"]["resolveProvider"] == True - assert registration["registerOptions"]["triggerCharacters"] == [ - ".", - "@", - "#", - "*", - " ", - ] - self.completions_available.set() - if registration["method"] == "workspace/executeCommand": - if "java.intellicode.enable" in registration["registerOptions"]["commands"]: - self.intellicode_enable_command_available.set() - return - - async def lang_status_handler(params): - # TODO: Should we wait for - # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} - # Before proceeding? - if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": - self.service_ready_event.set() - - async def execute_client_command_handler(params): - assert params["command"] == "_java.reloadBundles.command" - assert params["arguments"] == [] - return [] - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - async def do_nothing(params): - return - - self.server.on_request("client/registerCapability", register_capability_handler) - self.server.on_notification("language/status", lang_status_handler) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - - async with super().start_server(): - self.logger.log("Starting EclipseJDTLS server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 - assert "completionProvider" not in init_response["capabilities"] - assert "executeCommandProvider" not in init_response["capabilities"] - - self.server.notify.initialized({}) - - self.server.notify.workspace_did_change_configuration( - {"settings": initialize_params["initializationOptions"]["settings"]} - ) - - await self.intellicode_enable_command_available.wait() - - java_intellisense_members_path = self.runtime_dependency_paths.intellisense_members_path - assert os.path.exists(java_intellisense_members_path) - intellicode_enable_result = await self.server.send.execute_command( - { - "command": "java.intellicode.enable", - "arguments": [True, java_intellisense_members_path], - } - ) - assert intellicode_enable_result - - # TODO: Add comments about why we wait here, and how this can be optimized - await self.service_ready_event.wait() - - yield self diff --git a/src/multilspy/language_servers/eclipse_jdtls/initialize_params.json b/src/multilspy/language_servers/eclipse_jdtls/initialize_params.json deleted file mode 100644 index b65b7b62..00000000 --- a/src/multilspy/language_servers/eclipse_jdtls/initialize_params.json +++ /dev/null @@ -1,849 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "clientInfo": { - "name": "Visual Studio Code - Insiders", - "version": "1.77.0-insider" - }, - "locale": "en", - "rootPath": "repository_absolute_path", - "rootUri": "pathlib.Path(repository_absolute_path).as_uri()", - "capabilities": { - "workspace": { - "applyEdit": true, - "workspaceEdit": { - "documentChanges": true, - "resourceOperations": [ - "create", - "rename", - "delete" - ], - "failureHandling": "textOnlyTransactional", - "normalizesLineEndings": true, - "changeAnnotationSupport": { - "groupsOnLabel": true - } - }, - "didChangeConfiguration": { - "dynamicRegistration": true - }, - "didChangeWatchedFiles": { - "dynamicRegistration": true, - "relativePatternSupport": true - }, - "symbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "resolveSupport": { - "properties": [ - "location.range" - ] - } - }, - "codeLens": { - "refreshSupport": true - }, - "executeCommand": { - "dynamicRegistration": true - }, - "configuration": true, - "workspaceFolders": true, - "semanticTokens": { - "refreshSupport": true - }, - "fileOperations": { - "dynamicRegistration": true, - "didCreate": true, - "didRename": true, - "didDelete": true, - "willCreate": true, - "willRename": true, - "willDelete": true - }, - "inlineValue": { - "refreshSupport": true - }, - "inlayHint": { - "refreshSupport": true - }, - "diagnostics": { - "refreshSupport": true - } - }, - "textDocument": { - "publishDiagnostics": { - "relatedInformation": true, - "versionSupport": false, - "tagSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "codeDescriptionSupport": true, - "dataSupport": true - }, - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - }, - "completion": { - "dynamicRegistration": true, - "contextSupport": true, - "completionItem": { - "snippetSupport": false, - "commitCharactersSupport": true, - "documentationFormat": [ - "markdown", - "plaintext" - ], - "deprecatedSupport": true, - "preselectSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "insertReplaceSupport": false, - "resolveSupport": { - "properties": [ - "documentation", - "detail", - "additionalTextEdits" - ] - }, - "insertTextModeSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "labelDetailsSupport": true - }, - "insertTextMode": 2, - "completionItemKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25 - ] - }, - "completionList": { - "itemDefaults": [ - "commitCharacters", - "editRange", - "insertTextFormat", - "insertTextMode" - ] - } - }, - "hover": { - "dynamicRegistration": true, - "contentFormat": [ - "markdown", - "plaintext" - ] - }, - "signatureHelp": { - "dynamicRegistration": true, - "signatureInformation": { - "documentationFormat": [ - "markdown", - "plaintext" - ], - "parameterInformation": { - "labelOffsetSupport": true - }, - "activeParameterSupport": true - }, - "contextSupport": true - }, - "definition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "references": { - "dynamicRegistration": true - }, - "documentHighlight": { - "dynamicRegistration": true - }, - "documentSymbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "hierarchicalDocumentSymbolSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "labelSupport": true - }, - "codeAction": { - "dynamicRegistration": true, - "isPreferredSupport": true, - "disabledSupport": true, - "dataSupport": true, - "resolveSupport": { - "properties": [ - "edit" - ] - }, - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "", - "quickfix", - "refactor", - "refactor.extract", - "refactor.inline", - "refactor.rewrite", - "source", - "source.organizeImports" - ] - } - }, - "honorsChangeAnnotations": false - }, - "codeLens": { - "dynamicRegistration": true - }, - "formatting": { - "dynamicRegistration": true - }, - "rangeFormatting": { - "dynamicRegistration": true - }, - "onTypeFormatting": { - "dynamicRegistration": true - }, - "rename": { - "dynamicRegistration": true, - "prepareSupport": true, - "prepareSupportDefaultBehavior": 1, - "honorsChangeAnnotations": true - }, - "documentLink": { - "dynamicRegistration": true, - "tooltipSupport": true - }, - "typeDefinition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "implementation": { - "dynamicRegistration": true, - "linkSupport": true - }, - "colorProvider": { - "dynamicRegistration": true - }, - "foldingRange": { - "dynamicRegistration": true, - "rangeLimit": 5000, - "lineFoldingOnly": true, - "foldingRangeKind": { - "valueSet": [ - "comment", - "imports", - "region" - ] - }, - "foldingRange": { - "collapsedText": false - } - }, - "declaration": { - "dynamicRegistration": true, - "linkSupport": true - }, - "selectionRange": { - "dynamicRegistration": true - }, - "callHierarchy": { - "dynamicRegistration": true - }, - "semanticTokens": { - "dynamicRegistration": true, - "tokenTypes": [ - "namespace", - "type", - "class", - "enum", - "interface", - "struct", - "typeParameter", - "parameter", - "variable", - "property", - "enumMember", - "event", - "function", - "method", - "macro", - "keyword", - "modifier", - "comment", - "string", - "number", - "regexp", - "operator", - "decorator" - ], - "tokenModifiers": [ - "declaration", - "definition", - "readonly", - "static", - "deprecated", - "abstract", - "async", - "modification", - "documentation", - "defaultLibrary" - ], - "formats": [ - "relative" - ], - "requests": { - "range": true, - "full": { - "delta": true - } - }, - "multilineTokenSupport": false, - "overlappingTokenSupport": false, - "serverCancelSupport": true, - "augmentsSyntaxTokens": true - }, - "linkedEditingRange": { - "dynamicRegistration": true - }, - "typeHierarchy": { - "dynamicRegistration": true - }, - "inlineValue": { - "dynamicRegistration": true - }, - "inlayHint": { - "dynamicRegistration": true, - "resolveSupport": { - "properties": [ - "tooltip", - "textEdits", - "label.tooltip", - "label.location", - "label.command" - ] - } - }, - "diagnostic": { - "dynamicRegistration": true, - "relatedDocumentSupport": false - } - }, - "window": { - "showMessage": { - "messageActionItem": { - "additionalPropertiesSupport": true - } - }, - "showDocument": { - "support": true - }, - "workDoneProgress": true - }, - "general": { - "staleRequestSupport": { - "cancel": true, - "retryOnContentModified": [ - "textDocument/semanticTokens/full", - "textDocument/semanticTokens/range", - "textDocument/semanticTokens/full/delta" - ] - }, - "regularExpressions": { - "engine": "ECMAScript", - "version": "ES2020" - }, - "markdown": { - "parser": "marked", - "version": "1.1.0" - }, - "positionEncodings": [ - "utf-16" - ] - }, - "notebookDocument": { - "synchronization": { - "dynamicRegistration": true, - "executionSummarySupport": true - } - } - }, - "initializationOptions": { - "bundles": [ - "intellicode-core.jar" - ], - "workspaceFolders": "[pathlib.Path(repository_absolute_path).as_uri()]", - "settings": { - "java": { - "home": null, - "jdt": { - "ls": { - "java": { - "home": null - }, - "vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx1G -Xms100m -Xlog:disable", - "lombokSupport": { - "enabled": true - }, - "protobufSupport": { - "enabled": true - }, - "androidSupport": { - "enabled": true - } - } - }, - "errors": { - "incompleteClasspath": { - "severity": "error" - } - }, - "configuration": { - "checkProjectSettingsExclusions": false, - "updateBuildConfiguration": "interactive", - "maven": { - "userSettings": null, - "globalSettings": null, - "notCoveredPluginExecutionSeverity": "warning", - "defaultMojoExecutionAction": "ignore" - }, - "workspaceCacheLimit": 90, - "runtimes": [ - { - "name": "JavaSE-21", - "path": "static/vscode-java/extension/jre/21.0.7-linux-x86_64", - "default": true - } - ] - }, - "trace": { - "server": "verbose" - }, - "import": { - "maven": { - "enabled": true, - "offline": { - "enabled": false - }, - "disableTestClasspathFlag": false - }, - "gradle": { - "enabled": true, - "wrapper": { - "enabled": true - }, - "version": null, - "home": "abs(static/gradle-7.3.3)", - "java": { - "home": "abs(static/launch_jres/21.0.7-linux-x86_64)" - }, - "offline": { - "enabled": false - }, - "arguments": null, - "jvmArguments": null, - "user": { - "home": null - }, - "annotationProcessing": { - "enabled": true - } - }, - "exclusions": [ - "**/node_modules/**", - "**/.metadata/**", - "**/archetype-resources/**", - "**/META-INF/maven/**" - ], - "generatesMetadataFilesAtProjectRoot": false - }, - "maven": { - "downloadSources": true, - "updateSnapshots": true - }, - "eclipse": { - "downloadSources": true - }, - "referencesCodeLens": { - "enabled": true - }, - "signatureHelp": { - "enabled": true, - "description": { - "enabled": true - } - }, - "implementationsCodeLens": { - "enabled": true - }, - "format": { - "enabled": true, - "settings": { - "url": null, - "profile": null - }, - "comments": { - "enabled": true - }, - "onType": { - "enabled": true - }, - "insertSpaces": true, - "tabSize": 4 - }, - "saveActions": { - "organizeImports": false - }, - "project": { - "referencedLibraries": [ - "lib/**/*.jar" - ], - "importOnFirstTimeStartup": "automatic", - "importHint": true, - "resourceFilters": [ - "node_modules", - "\\.git" - ], - "encoding": "ignore", - "exportJar": { - "targetPath": "${workspaceFolder}/${workspaceFolderBasename}.jar" - } - }, - "contentProvider": { - "preferred": null - }, - "autobuild": { - "enabled": true - }, - "maxConcurrentBuilds": 1, - "recommendations": { - "dependency": { - "analytics": { - "show": true - } - } - }, - "completion": { - "maxResults": 0, - "enabled": true, - "guessMethodArguments": true, - "favoriteStaticMembers": [ - "org.junit.Assert.*", - "org.junit.Assume.*", - "org.junit.jupiter.api.Assertions.*", - "org.junit.jupiter.api.Assumptions.*", - "org.junit.jupiter.api.DynamicContainer.*", - "org.junit.jupiter.api.DynamicTest.*", - "org.mockito.Mockito.*", - "org.mockito.ArgumentMatchers.*", - "org.mockito.Answers.*" - ], - "filteredTypes": [ - "java.awt.*", - "com.sun.*", - "sun.*", - "jdk.*", - "org.graalvm.*", - "io.micrometer.shaded.*" - ], - "importOrder": [ - "#", - "java", - "javax", - "org", - "com", - "" - ], - "postfix": { - "enabled": false - }, - "matchCase": "off" - }, - "foldingRange": { - "enabled": true - }, - "progressReports": { - "enabled": false - }, - "codeGeneration": { - "hashCodeEquals": { - "useJava7Objects": false, - "useInstanceof": false - }, - "useBlocks": false, - "generateComments": false, - "toString": { - "template": "${object.className} [${member.name()}=${member.value}, ${otherMembers}]", - "codeStyle": "STRING_CONCATENATION", - "skipNullValues": false, - "listArrayContents": true, - "limitElements": 0 - }, - "insertionLocation": "afterCursor" - }, - "selectionRange": { - "enabled": true - }, - "showBuildStatusOnStart": { - "enabled": "notification" - }, - "server": { - "launchMode": "Standard" - }, - "sources": { - "organizeImports": { - "starThreshold": 99, - "staticStarThreshold": 99 - } - }, - "imports": { - "gradle": { - "wrapper": { - "checksums": [] - } - } - }, - "templates": { - "fileHeader": [], - "typeComment": [] - }, - "references": { - "includeAccessors": true, - "includeDecompiledSources": true - }, - "typeHierarchy": { - "lazyLoad": false - }, - "settings": { - "url": null - }, - "symbols": { - "includeSourceMethodDeclarations": false - }, - "quickfix": { - "showAt": "line" - }, - "inlayHints": { - "parameterNames": { - "enabled": "literals", - "exclusions": [] - } - }, - "codeAction": { - "sortMembers": { - "avoidVolatileChanges": true - } - }, - "compile": { - "nullAnalysis": { - "nonnull": [ - "javax.annotation.Nonnull", - "org.eclipse.jdt.annotation.NonNull", - "org.springframework.lang.NonNull" - ], - "nullable": [ - "javax.annotation.Nullable", - "org.eclipse.jdt.annotation.Nullable", - "org.springframework.lang.Nullable" - ], - "mode": "automatic" - } - }, - "cleanup": { - "actionsOnSave": [] - }, - "sharedIndexes": { - "enabled": "auto", - "location": "" - }, - "refactoring": { - "extract": { - "interface": { - "replace": true - } - } - }, - "debug": { - "logLevel": "verbose", - "settings": { - "showHex": false, - "showStaticVariables": false, - "showQualifiedNames": false, - "showLogicalStructure": true, - "showToString": true, - "maxStringLength": 0, - "numericPrecision": 0, - "hotCodeReplace": "manual", - "enableRunDebugCodeLens": true, - "forceBuildBeforeLaunch": true, - "onBuildFailureProceed": false, - "console": "integratedTerminal", - "exceptionBreakpoint": { - "skipClasses": [] - }, - "stepping": { - "skipClasses": [], - "skipSynthetics": false, - "skipStaticInitializers": false, - "skipConstructors": false - }, - "jdwp": { - "limitOfVariablesPerJdwpRequest": 100, - "requestTimeout": 3000, - "async": "auto" - }, - "vmArgs": "" - } - }, - "silentNotification": false, - "dependency": { - "showMembers": false, - "syncWithFolderExplorer": true, - "autoRefresh": true, - "refreshDelay": 2000, - "packagePresentation": "flat" - }, - "help": { - "firstView": "auto", - "showReleaseNotes": true, - "collectErrorLog": false - }, - "test": { - "defaultConfig": "", - "config": {} - } - } - }, - "extendedClientCapabilities": { - "progressReportProvider": false, - "classFileContentsSupport": true, - "overrideMethodsPromptSupport": true, - "hashCodeEqualsPromptSupport": true, - "advancedOrganizeImportsSupport": true, - "generateToStringPromptSupport": true, - "advancedGenerateAccessorsSupport": true, - "generateConstructorsPromptSupport": true, - "generateDelegateMethodsPromptSupport": true, - "advancedExtractRefactoringSupport": true, - "inferSelectionSupport": [ - "extractMethod", - "extractVariable", - "extractField" - ], - "moveRefactoringSupport": true, - "clientHoverProvider": true, - "clientDocumentSymbolProvider": true, - "gradleChecksumWrapperPromptSupport": true, - "resolveAdditionalTextEditsSupport": true, - "advancedIntroduceParameterRefactoringSupport": true, - "actionableRuntimeNotificationSupport": true, - "shouldLanguageServerExitOnShutdown": true, - "onCompletionItemSelectedCommand": "editor.action.triggerParameterHints", - "extractInterfaceSupport": true, - "advancedUpgradeGradleSupport": true - }, - "triggerFiles": [] - }, - "trace": "verbose", - "workspaceFolders": "[\n {\n \"uri\": pathlib.Path(repository_absolute_path).as_uri(),\n \"name\": os.path.basename(repository_absolute_path),\n }\n ]" -} \ No newline at end of file diff --git a/src/multilspy/language_servers/eclipse_jdtls/runtime_dependencies.json b/src/multilspy/language_servers/eclipse_jdtls/runtime_dependencies.json deleted file mode 100644 index 1b000405..00000000 --- a/src/multilspy/language_servers/eclipse_jdtls/runtime_dependencies.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "_description": "This file lists the runtime dependencies for the Java Language Server", - "gradle": { - "platform-agnostic": { - "url": "https://services.gradle.org/distributions/gradle-7.3.3-bin.zip", - "archiveType": "zip", - "relative_extraction_path": "." - } - }, - "vscode-java": { - "darwin-arm64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix", - "archiveType": "zip", - "relative_extraction_path": "vscode-java" - }, - "osx-arm64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix", - "archiveType": "zip", - "relative_extraction_path": "vscode-java", - "jre_home_path": "extension/jre/21.0.7-macosx-aarch64", - "jre_path": "extension/jre/21.0.7-macosx-aarch64/bin/java", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", - "jdtls_readonly_config_path": "extension/server/config_mac_arm" - }, - "osx-x64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix", - "archiveType": "zip", - "relative_extraction_path": "vscode-java", - "jre_home_path": "extension/jre/21.0.7-macosx-x86_64", - "jre_path": "extension/jre/21.0.7-macosx-x86_64/bin/java", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", - "jdtls_readonly_config_path": "extension/server/config_mac" - }, - "linux-arm64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix", - "archiveType": "zip", - "relative_extraction_path": "vscode-java" - }, - "linux-x64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix", - "archiveType": "zip", - "relative_extraction_path": "vscode-java", - "jre_home_path": "extension/jre/21.0.7-linux-x86_64", - "jre_path": "extension/jre/21.0.7-linux-x86_64/bin/java", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", - "jdtls_readonly_config_path": "extension/server/config_linux" - }, - "win-x64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix", - "archiveType": "zip", - "relative_extraction_path": "vscode-java", - "jre_home_path": "extension/jre/21.0.7-win32-x86_64", - "jre_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", - "jdtls_readonly_config_path": "extension/server/config_win" - } - }, - "intellicode": { - "platform-agnostic": { - "url": "https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/1.2.30/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage", - "alternate_url": "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/VisualStudioExptTeam/vsextensions/vscodeintellicode/1.2.30/vspackage", - "archiveType": "zip", - "relative_extraction_path": "intellicode", - "intellicode_jar_path": "extension/dist/com.microsoft.jdtls.intellicode.core-0.7.0.jar", - "intellisense_members_path": "extension/dist/bundledModels/java_intellisense-members" - } - } -} diff --git a/src/multilspy/language_servers/gopls/gopls.py b/src/multilspy/language_servers/gopls/gopls.py deleted file mode 100644 index 0e5574a0..00000000 --- a/src/multilspy/language_servers/gopls/gopls.py +++ /dev/null @@ -1,154 +0,0 @@ -import asyncio -import json -import logging -import os -import pathlib -import subprocess -from contextlib import asynccontextmanager -from typing import AsyncIterator, List - -from overrides import override - -from multilspy import multilspy_types -from multilspy.lsp_protocol_handler import lsp_types -from multilspy.multilspy_exceptions import MultilspyException -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import Error, ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig - - -class Gopls(LanguageServer): - """ - Provides Go specific instantiation of the LanguageServer class using gopls. - """ - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - # For Go projects, we should ignore: - # - vendor: third-party dependencies vendored into the project - # - node_modules: if the project has JavaScript components - # - dist/build: common output directories - return super().is_ignored_dirname(dirname) or dirname in ["vendor", "node_modules", "dist", "build"] - - @staticmethod - def _get_go_version(): - """Get the installed Go version or None if not found.""" - try: - result = subprocess.run(['go', 'version'], capture_output=True, text=True) - if result.returncode == 0: - return result.stdout.strip() - except FileNotFoundError: - return None - return None - - @staticmethod - def _get_gopls_version(): - """Get the installed gopls version or None if not found.""" - try: - result = subprocess.run(['gopls', 'version'], capture_output=True, text=True) - if result.returncode == 0: - return result.stdout.strip() - except FileNotFoundError: - return None - return None - - @classmethod - def setup_runtime_dependency(cls): - """ - Check if required Go runtime dependencies are available. - Raises RuntimeError with helpful message if dependencies are missing. - """ - go_version = cls._get_go_version() - if not go_version: - raise RuntimeError("Go is not installed. Please install Go from https://golang.org/doc/install and make sure it is added to your PATH.") - - gopls_version = cls._get_gopls_version() - if not gopls_version: - raise RuntimeError( - "Found a Go version but gopls is not installed.\n" - "Please install gopls as described in https://pkg.go.dev/golang.org/x/tools/gopls#section-readme\n\n" - "After installation, make sure it is added to your PATH (it might be installed in a different location than Go)." - ) - - return True - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - self.setup_runtime_dependency() - - super().__init__( - config, - logger, - repository_root_path, - ProcessLaunchInfo(cmd="gopls", cwd=repository_root_path), - "go", - ) - self.server_ready = asyncio.Event() - self.request_id = 0 - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the TypeScript Language Server. - """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["Gopls"]: - """Start gopls server process""" - async def register_capability_handler(params): - return - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - async def do_nothing(params): - return - - 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) - - async with super().start_server(): - self.logger.log("Starting gopls server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - - # Verify server capabilities - assert "textDocumentSync" in init_response["capabilities"] - assert "completionProvider" in init_response["capabilities"] - assert "definitionProvider" in init_response["capabilities"] - - self.server.notify.initialized({}) - self.completions_available.set() - - # gopls server is typically ready immediately after initialization - self.server_ready.set() - await self.server_ready.wait() - - yield self diff --git a/src/multilspy/language_servers/gopls/initialize_params.json b/src/multilspy/language_servers/gopls/initialize_params.json deleted file mode 100644 index 40592e1a..00000000 --- a/src/multilspy/language_servers/gopls/initialize_params.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "locale": "en", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": { - "textDocument": { - "synchronization": { - "didSave": true, - "dynamicRegistration": true - }, - "completion": { - "dynamicRegistration": true, - "completionItem": { - "snippetSupport": true - } - }, - "definition": { - "dynamicRegistration": true - }, - "documentSymbol": { - "dynamicRegistration": true, - "hierarchicalDocumentSymbolSupport": true, - "symbolKind": { - "valueSet": [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 - ] - } - } - }, - "workspace": { - "workspaceFolders": true, - "didChangeConfiguration": { - "dynamicRegistration": true - } - } - }, - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/intelephense/initialize_params.json b/src/multilspy/language_servers/intelephense/initialize_params.json deleted file mode 100644 index 4330560e..00000000 --- a/src/multilspy/language_servers/intelephense/initialize_params.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "locale": "en", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": { - "textDocument": { - "synchronization": { - "didSave": true, - "dynamicRegistration": true - }, - "completion": { - "dynamicRegistration": true, - "completionItem": { - "snippetSupport": true - } - }, - "definition": { - "dynamicRegistration": true - } - }, - "workspace": { - "workspaceFolders": true, - "didChangeConfiguration": { - "dynamicRegistration": true - } - } - }, - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/intelephense/intelephense.py b/src/multilspy/language_servers/intelephense/intelephense.py deleted file mode 100644 index d2ad9117..00000000 --- a/src/multilspy/language_servers/intelephense/intelephense.py +++ /dev/null @@ -1,200 +0,0 @@ -""" -Provides PHP specific instantiation of the LanguageServer class using Intelephense. -""" - -import asyncio -import json -import shutil -import logging -import os -import subprocess -import pathlib -from contextlib import asynccontextmanager -from time import sleep -from typing import AsyncIterator - -from overrides import override - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_utils import PlatformUtils, PlatformId - -class Intelephense(LanguageServer): - """ - Provides PHP specific instantiation of the LanguageServer class using Intelephense. - """ - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - # For PHP projects, we should ignore: - # - vendor: third-party dependencies managed by Composer - # - node_modules: if the project has JavaScript components - # - cache: commonly used for caching - return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "cache"] - - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: - """ - Setup runtime dependencies for Intelephense. - """ - platform_id = PlatformUtils.get_platform_id() - - valid_platforms = [ - PlatformId.LINUX_x64, - PlatformId.LINUX_arm64, - PlatformId.OSX, - PlatformId.OSX_x64, - PlatformId.OSX_arm64, - 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" - - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: - d = json.load(f) - del d["_description"] - - runtime_dependencies = d.get("runtimeDependencies", []) - intelephense_ls_dir = os.path.join(os.path.dirname(__file__), "static", "php-lsp") - - # Verify both node and npm are installed - is_node_installed = shutil.which('node') is not None - assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again." - is_npm_installed = shutil.which('npm') is not None - assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again." - - # Install intelephense if not already installed - if not os.path.exists(intelephense_ls_dir): - os.makedirs(intelephense_ls_dir, exist_ok=True) - for dependency in runtime_dependencies: - # Windows doesn't support the 'user' parameter and doesn't have pwd module - if PlatformUtils.get_platform_id().value.startswith("win"): - subprocess.run( - dependency["command"], - shell=True, - check=True, - cwd=intelephense_ls_dir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL - ) - else: - # On Unix-like systems, run as non-root user - import pwd - user = pwd.getpwuid(os.getuid()).pw_name - subprocess.run( - dependency["command"], - shell=True, - check=True, - user=user, - cwd=intelephense_ls_dir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL - ) - - intelephense_executable_path = os.path.join(intelephense_ls_dir, "node_modules", ".bin", "intelephense") - assert os.path.exists(intelephense_executable_path), "intelephense executable not found. Please install intelephense and try again." - - return f"{intelephense_executable_path} --stdio" - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - # Setup runtime dependencies before initializing - intelephense_cmd = self.setup_runtime_dependencies(logger, config) - - super().__init__( - config, - logger, - repository_root_path, - ProcessLaunchInfo(cmd=intelephense_cmd, cwd=repository_root_path), - "php" - ) - self.server_ready = asyncio.Event() - self.request_id = 0 - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the TypeScript Language Server. - """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["Intelephense"]: - """Start Intelephense server process""" - async def register_capability_handler(params): - return - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - async def do_nothing(params): - return - - 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) - - async with super().start_server(): - self.logger.log("Starting Intelephense server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - self.logger.log( - "After sent initialize params", - logging.INFO, - ) - - # Verify server capabilities - assert "textDocumentSync" in init_response["capabilities"] - assert "completionProvider" in init_response["capabilities"] - assert "definitionProvider" in init_response["capabilities"] - - self.server.notify.initialized({}) - self.completions_available.set() - - # Intelephense server is typically ready immediately after initialization - self.server_ready.set() - await self.server_ready.wait() - - yield self - - @override - # For some reason, the LS may need longer to process this, so we just retry - async def _send_references_request(self, relative_file_path: str, line: int, column: int): - # TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is - # despite the LS having processed requests already. I don't know what causes this, but sleeping - # one second helps. It may be that sleeping only once is enough but that's hard to reliably test. - # May be related to the time it takes to read the files or something like that. - # The sleeping doesn't seem to be needed on all systems - sleep(1) - return await super()._send_references_request(relative_file_path, line, column) - - @override - async def _send_definition_request(self, definition_params: DefinitionParams): - # TODO: same as above, also only a problem if the definition is in another file - sleep(1) - return await super()._send_definition_request(definition_params) diff --git a/src/multilspy/language_servers/intelephense/runtime_dependencies.json b/src/multilspy/language_servers/intelephense/runtime_dependencies.json deleted file mode 100644 index e3a6d7e1..00000000 --- a/src/multilspy/language_servers/intelephense/runtime_dependencies.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "_description": "Used to download the runtime dependencies for running intelephense. Obtained from https://www.npmjs.com/package/intelephense", - "runtimeDependencies": [ - { - "id": "intelephense", - "description": "Intelephense package for Linux, OSX, and Windows. Both x64 and arm64 are supported.", - "command": "npm install --prefix ./ intelephense@1.14.4" - } - ] -} diff --git a/src/multilspy/language_servers/jedi_language_server/initialize_params.json b/src/multilspy/language_servers/jedi_language_server/initialize_params.json deleted file mode 100644 index 5a04872c..00000000 --- a/src/multilspy/language_servers/jedi_language_server/initialize_params.json +++ /dev/null @@ -1,905 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "clientInfo": { - "name": "Visual Studio Code - Insiders", - "version": "1.81.0-insider" - }, - "locale": "en", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": { - "workspace": { - "applyEdit": true, - "workspaceEdit": { - "documentChanges": true, - "resourceOperations": [ - "create", - "rename", - "delete" - ], - "failureHandling": "textOnlyTransactional", - "normalizesLineEndings": true, - "changeAnnotationSupport": { - "groupsOnLabel": true - } - }, - "configuration": true, - "didChangeWatchedFiles": { - "dynamicRegistration": true, - "relativePatternSupport": true - }, - "symbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "resolveSupport": { - "properties": [ - "location.range" - ] - } - }, - "codeLens": { - "refreshSupport": true - }, - "executeCommand": { - "dynamicRegistration": true - }, - "didChangeConfiguration": { - "dynamicRegistration": true - }, - "workspaceFolders": true, - "semanticTokens": { - "refreshSupport": true - }, - "fileOperations": { - "dynamicRegistration": true, - "didCreate": true, - "didRename": true, - "didDelete": true, - "willCreate": true, - "willRename": true, - "willDelete": true - }, - "inlineValue": { - "refreshSupport": true - }, - "inlayHint": { - "refreshSupport": true - }, - "diagnostics": { - "refreshSupport": true - } - }, - "textDocument": { - "publishDiagnostics": { - "relatedInformation": true, - "versionSupport": false, - "tagSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "codeDescriptionSupport": true, - "dataSupport": true - }, - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - }, - "completion": { - "dynamicRegistration": true, - "contextSupport": true, - "completionItem": { - "snippetSupport": true, - "commitCharactersSupport": true, - "documentationFormat": [ - "markdown", - "plaintext" - ], - "deprecatedSupport": true, - "preselectSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "insertReplaceSupport": true, - "resolveSupport": { - "properties": [ - "documentation", - "detail", - "additionalTextEdits" - ] - }, - "insertTextModeSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "labelDetailsSupport": true - }, - "insertTextMode": 2, - "completionItemKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25 - ] - }, - "completionList": { - "itemDefaults": [ - "commitCharacters", - "editRange", - "insertTextFormat", - "insertTextMode" - ] - } - }, - "hover": { - "dynamicRegistration": true, - "contentFormat": [ - "markdown", - "plaintext" - ] - }, - "signatureHelp": { - "dynamicRegistration": true, - "signatureInformation": { - "documentationFormat": [ - "markdown", - "plaintext" - ], - "parameterInformation": { - "labelOffsetSupport": true - }, - "activeParameterSupport": true - }, - "contextSupport": true - }, - "definition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "references": { - "dynamicRegistration": true - }, - "documentHighlight": { - "dynamicRegistration": true - }, - "documentSymbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "hierarchicalDocumentSymbolSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "labelSupport": true - }, - "codeAction": { - "dynamicRegistration": true, - "isPreferredSupport": true, - "disabledSupport": true, - "dataSupport": true, - "resolveSupport": { - "properties": [ - "edit" - ] - }, - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "", - "quickfix", - "refactor", - "refactor.extract", - "refactor.inline", - "refactor.rewrite", - "source", - "source.organizeImports" - ] - } - }, - "honorsChangeAnnotations": false - }, - "codeLens": { - "dynamicRegistration": true - }, - "formatting": { - "dynamicRegistration": true - }, - "rangeFormatting": { - "dynamicRegistration": true - }, - "onTypeFormatting": { - "dynamicRegistration": true - }, - "rename": { - "dynamicRegistration": true, - "prepareSupport": true, - "prepareSupportDefaultBehavior": 1, - "honorsChangeAnnotations": true - }, - "documentLink": { - "dynamicRegistration": true, - "tooltipSupport": true - }, - "typeDefinition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "implementation": { - "dynamicRegistration": true, - "linkSupport": true - }, - "colorProvider": { - "dynamicRegistration": true - }, - "foldingRange": { - "dynamicRegistration": true, - "rangeLimit": 5000, - "lineFoldingOnly": true, - "foldingRangeKind": { - "valueSet": [ - "comment", - "imports", - "region" - ] - }, - "foldingRange": { - "collapsedText": false - } - }, - "declaration": { - "dynamicRegistration": true, - "linkSupport": true - }, - "selectionRange": { - "dynamicRegistration": true - }, - "callHierarchy": { - "dynamicRegistration": true - }, - "semanticTokens": { - "dynamicRegistration": true, - "tokenTypes": [ - "namespace", - "type", - "class", - "enum", - "interface", - "struct", - "typeParameter", - "parameter", - "variable", - "property", - "enumMember", - "event", - "function", - "method", - "macro", - "keyword", - "modifier", - "comment", - "string", - "number", - "regexp", - "operator", - "decorator" - ], - "tokenModifiers": [ - "declaration", - "definition", - "readonly", - "static", - "deprecated", - "abstract", - "async", - "modification", - "documentation", - "defaultLibrary" - ], - "formats": [ - "relative" - ], - "requests": { - "range": true, - "full": { - "delta": true - } - }, - "multilineTokenSupport": false, - "overlappingTokenSupport": false, - "serverCancelSupport": true, - "augmentsSyntaxTokens": false - }, - "linkedEditingRange": { - "dynamicRegistration": true - }, - "typeHierarchy": { - "dynamicRegistration": true - }, - "inlineValue": { - "dynamicRegistration": true - }, - "inlayHint": { - "dynamicRegistration": true, - "resolveSupport": { - "properties": [ - "tooltip", - "textEdits", - "label.tooltip", - "label.location", - "label.command" - ] - } - }, - "diagnostic": { - "dynamicRegistration": true, - "relatedDocumentSupport": false - } - }, - "window": { - "showMessage": { - "messageActionItem": { - "additionalPropertiesSupport": true - } - }, - "showDocument": { - "support": true - }, - "workDoneProgress": true - }, - "general": { - "staleRequestSupport": { - "cancel": true, - "retryOnContentModified": [ - "textDocument/semanticTokens/full", - "textDocument/semanticTokens/range", - "textDocument/semanticTokens/full/delta" - ] - }, - "regularExpressions": { - "engine": "ECMAScript", - "version": "ES2020" - }, - "markdown": { - "parser": "marked", - "version": "1.1.0", - "allowedTags": [ - "ul", - "li", - "p", - "code", - "blockquote", - "ol", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "hr", - "em", - "pre", - "table", - "thead", - "tbody", - "tr", - "th", - "td", - "div", - "del", - "a", - "strong", - "br", - "img", - "span" - ] - }, - "positionEncodings": [ - "utf-16" - ] - }, - "notebookDocument": { - "synchronization": { - "dynamicRegistration": true, - "executionSummarySupport": true - } - }, - "experimental": { - "snippetTextEdit": true, - "codeActionGroup": true, - "hoverActions": true, - "serverStatusNotification": true, - "colorDiagnosticOutput": true, - "openServerLogs": true, - "commands": { - "commands": [ - "rust-analyzer.runSingle", - "rust-analyzer.debugSingle", - "rust-analyzer.showReferences", - "rust-analyzer.gotoLocation", - "editor.action.triggerParameterHints" - ] - } - } - }, - "initializationOptions": { - "cargoRunner": null, - "runnables": { - "extraEnv": null, - "problemMatcher": [ - "$rustc" - ], - "command": null, - "extraArgs": [] - }, - "server": { - "path": null, - "extraEnv": null - }, - "trace": { - "server": "verbose", - "extension": true - }, - "debug": { - "engine": "auto", - "sourceFileMap": { - "/rustc/": "${env:USERPROFILE}/.rustup/toolchains//lib/rustlib/src/rust" - }, - "openDebugPane": false, - "engineSettings": {} - }, - "restartServerOnConfigChange": false, - "typing": { - "continueCommentsOnNewline": true, - "autoClosingAngleBrackets": { - "enable": false - } - }, - "diagnostics": { - "previewRustcOutput": false, - "useRustcErrorCode": false, - "disabled": [], - "enable": true, - "experimental": { - "enable": false - }, - "remapPrefix": {}, - "warningsAsHint": [], - "warningsAsInfo": [] - }, - "discoverProjectCommand": null, - "showUnlinkedFileNotification": true, - "showDependenciesExplorer": true, - "assist": { - "emitMustUse": false, - "expressionFillDefault": "todo" - }, - "cachePriming": { - "enable": true, - "numThreads": 0 - }, - "cargo": { - "autoreload": true, - "buildScripts": { - "enable": true, - "invocationLocation": "workspace", - "invocationStrategy": "per_workspace", - "overrideCommand": null, - "useRustcWrapper": true - }, - "cfgs": {}, - "extraArgs": [], - "extraEnv": {}, - "features": [], - "noDefaultFeatures": false, - "sysroot": "discover", - "sysrootSrc": null, - "target": null, - "unsetTest": [ - "core" - ] - }, - "checkOnSave": true, - "check": { - "allTargets": true, - "command": "check", - "extraArgs": [], - "extraEnv": {}, - "features": null, - "invocationLocation": "workspace", - "invocationStrategy": "per_workspace", - "noDefaultFeatures": null, - "overrideCommand": null, - "targets": null - }, - "completion": { - "autoimport": { - "enable": true - }, - "autoself": { - "enable": true - }, - "callable": { - "snippets": "fill_arguments" - }, - "limit": null, - "postfix": { - "enable": true - }, - "privateEditable": { - "enable": false - }, - "snippets": { - "custom": { - "Arc::new": { - "postfix": "arc", - "body": "Arc::new(${receiver})", - "requires": "std::sync::Arc", - "description": "Put the expression into an `Arc`", - "scope": "expr" - }, - "Rc::new": { - "postfix": "rc", - "body": "Rc::new(${receiver})", - "requires": "std::rc::Rc", - "description": "Put the expression into an `Rc`", - "scope": "expr" - }, - "Box::pin": { - "postfix": "pinbox", - "body": "Box::pin(${receiver})", - "requires": "std::boxed::Box", - "description": "Put the expression into a pinned `Box`", - "scope": "expr" - }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, - "Err": { - "postfix": "err", - "body": "Err(${receiver})", - "description": "Wrap the expression in a `Result::Err`", - "scope": "expr" - }, - "Some": { - "postfix": "some", - "body": "Some(${receiver})", - "description": "Wrap the expression in an `Option::Some`", - "scope": "expr" - } - } - } - }, - "files": { - "excludeDirs": [], - "watcher": "client" - }, - "highlightRelated": { - "breakPoints": { - "enable": true - }, - "closureCaptures": { - "enable": true - }, - "exitPoints": { - "enable": true - }, - "references": { - "enable": true - }, - "yieldPoints": { - "enable": true - } - }, - "hover": { - "actions": { - "debug": { - "enable": true - }, - "enable": true, - "gotoTypeDef": { - "enable": true - }, - "implementations": { - "enable": true - }, - "references": { - "enable": false - }, - "run": { - "enable": true - } - }, - "documentation": { - "enable": true, - "keywords": { - "enable": true - } - }, - "links": { - "enable": true - }, - "memoryLayout": { - "alignment": "hexadecimal", - "enable": true, - "niches": false, - "offset": "hexadecimal", - "size": "both" - } - }, - "imports": { - "granularity": { - "enforce": false, - "group": "crate" - }, - "group": { - "enable": true - }, - "merge": { - "glob": true - }, - "prefer": { - "no": { - "std": false - } - }, - "prefix": "plain" - }, - "inlayHints": { - "bindingModeHints": { - "enable": false - }, - "chainingHints": { - "enable": true - }, - "closingBraceHints": { - "enable": true, - "minLines": 25 - }, - "closureCaptureHints": { - "enable": false - }, - "closureReturnTypeHints": { - "enable": "never" - }, - "closureStyle": "impl_fn", - "discriminantHints": { - "enable": "never" - }, - "expressionAdjustmentHints": { - "enable": "never", - "hideOutsideUnsafe": false, - "mode": "prefix" - }, - "lifetimeElisionHints": { - "enable": "never", - "useParameterNames": false - }, - "maxLength": 25, - "parameterHints": { - "enable": true - }, - "reborrowHints": { - "enable": "never" - }, - "renderColons": true, - "typeHints": { - "enable": true, - "hideClosureInitialization": false, - "hideNamedConstructor": false - } - }, - "interpret": { - "tests": false - }, - "joinLines": { - "joinAssignments": true, - "joinElseIf": true, - "removeTrailingComma": true, - "unwrapTrivialBlock": true - }, - "lens": { - "debug": { - "enable": true - }, - "enable": true, - "forceCustomCommands": true, - "implementations": { - "enable": true - }, - "location": "above_name", - "references": { - "adt": { - "enable": false - }, - "enumVariant": { - "enable": false - }, - "method": { - "enable": false - }, - "trait": { - "enable": false - } - }, - "run": { - "enable": true - } - }, - "linkedProjects": [], - "lru": { - "capacity": null, - "query": { - "capacities": {} - } - }, - "notifications": { - "cargoTomlNotFound": true - }, - "numThreads": null, - "procMacro": { - "attributes": { - "enable": true - }, - "enable": true, - "ignored": {}, - "server": null - }, - "references": { - "excludeImports": false - }, - "rustc": { - "source": null - }, - "rustfmt": { - "extraArgs": [], - "overrideCommand": null, - "rangeFormatting": { - "enable": false - } - }, - "semanticHighlighting": { - "doc": { - "comment": { - "inject": { - "enable": true - } - } - }, - "nonStandardTokens": true, - "operator": { - "enable": true, - "specialization": { - "enable": false - } - }, - "punctuation": { - "enable": false, - "separate": { - "macro": { - "bang": false - } - }, - "specialization": { - "enable": false - } - }, - "strings": { - "enable": true - } - }, - "signatureInfo": { - "detail": "full", - "documentation": { - "enable": true - } - }, - "workspace": { - "symbols": { - "maxSymbols": 0 - } - } - }, - "trace": "verbose", - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/jedi_language_server/jedi_server.py b/src/multilspy/language_servers/jedi_language_server/jedi_server.py deleted file mode 100644 index 28c9d91d..00000000 --- a/src/multilspy/language_servers/jedi_language_server/jedi_server.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python. -""" - -import json -import logging -import os -import pathlib -from contextlib import asynccontextmanager -from typing import AsyncIterator, Tuple - -from overrides import override - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig - - -class JediServer(LanguageServer): - """ - Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python. - """ - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - 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", - ) - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"] - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the Jedi Language Server. - """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["JediServer"]: - """ - Starts the JEDI Language Server, waits for the server to be ready and yields the LanguageServer instance. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and ready to serve requests - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown - ``` - """ - - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def check_experimental_status(params): - if params["quiescent"] == True: - self.completions_available.set() - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - self.server.on_request("client/registerCapability", do_nothing) - self.server.on_notification("language/status", do_nothing) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - self.server.on_notification("experimental/serverStatus", check_experimental_status) - - async with super().start_server(): - self.logger.log("Starting jedi-language-server server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 - assert "completionProvider" in init_response["capabilities"] - assert init_response["capabilities"]["completionProvider"] == { - "triggerCharacters": [".", "'", '"'], - "resolveProvider": True, - } - - self.server.notify.initialized({}) - - yield self diff --git a/src/multilspy/language_servers/kotlin_language_server/initialize_params.json b/src/multilspy/language_servers/kotlin_language_server/initialize_params.json deleted file mode 100644 index a7b2ec0a..00000000 --- a/src/multilspy/language_servers/kotlin_language_server/initialize_params.json +++ /dev/null @@ -1,521 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "clientInfo": { - "name": "Multilspy Kotlin Client", - "version": "1.0.0" - }, - "locale": "en", - "rootPath": "repository_absolute_path", - "rootUri": "pathlib.Path(repository_absolute_path).as_uri()", - "capabilities": { - "workspace": { - "applyEdit": true, - "workspaceEdit": { - "documentChanges": true, - "resourceOperations": [ - "create", - "rename", - "delete" - ], - "failureHandling": "textOnlyTransactional", - "normalizesLineEndings": true, - "changeAnnotationSupport": { - "groupsOnLabel": true - } - }, - "didChangeConfiguration": { - "dynamicRegistration": true - }, - "didChangeWatchedFiles": { - "dynamicRegistration": true, - "relativePatternSupport": true - }, - "symbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "resolveSupport": { - "properties": [ - "location.range" - ] - } - }, - "codeLens": { - "refreshSupport": true - }, - "executeCommand": { - "dynamicRegistration": true - }, - "configuration": true, - "workspaceFolders": true, - "semanticTokens": { - "refreshSupport": true - }, - "fileOperations": { - "dynamicRegistration": true, - "didCreate": true, - "didRename": true, - "didDelete": true, - "willCreate": true, - "willRename": true, - "willDelete": true - }, - "inlineValue": { - "refreshSupport": true - }, - "inlayHint": { - "refreshSupport": true - }, - "diagnostics": { - "refreshSupport": true - } - }, - "textDocument": { - "publishDiagnostics": { - "relatedInformation": true, - "versionSupport": false, - "tagSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "codeDescriptionSupport": true, - "dataSupport": true - }, - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - }, - "completion": { - "dynamicRegistration": true, - "contextSupport": true, - "completionItem": { - "snippetSupport": false, - "commitCharactersSupport": true, - "documentationFormat": [ - "markdown", - "plaintext" - ], - "deprecatedSupport": true, - "preselectSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "insertReplaceSupport": false, - "resolveSupport": { - "properties": [ - "documentation", - "detail", - "additionalTextEdits" - ] - }, - "insertTextModeSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "labelDetailsSupport": true - }, - "insertTextMode": 2, - "completionItemKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25 - ] - }, - "completionList": { - "itemDefaults": [ - "commitCharacters", - "editRange", - "insertTextFormat", - "insertTextMode" - ] - } - }, - "hover": { - "dynamicRegistration": true, - "contentFormat": [ - "markdown", - "plaintext" - ] - }, - "signatureHelp": { - "dynamicRegistration": true, - "signatureInformation": { - "documentationFormat": [ - "markdown", - "plaintext" - ], - "parameterInformation": { - "labelOffsetSupport": true - }, - "activeParameterSupport": true - }, - "contextSupport": true - }, - "definition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "references": { - "dynamicRegistration": true - }, - "documentHighlight": { - "dynamicRegistration": true - }, - "documentSymbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "hierarchicalDocumentSymbolSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "labelSupport": true - }, - "codeAction": { - "dynamicRegistration": true, - "isPreferredSupport": true, - "disabledSupport": true, - "dataSupport": true, - "resolveSupport": { - "properties": [ - "edit" - ] - }, - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "", - "quickfix", - "refactor", - "refactor.extract", - "refactor.inline", - "refactor.rewrite", - "source", - "source.organizeImports" - ] - } - }, - "honorsChangeAnnotations": false - }, - "codeLens": { - "dynamicRegistration": true - }, - "formatting": { - "dynamicRegistration": true - }, - "rangeFormatting": { - "dynamicRegistration": true - }, - "onTypeFormatting": { - "dynamicRegistration": true - }, - "rename": { - "dynamicRegistration": true, - "prepareSupport": true, - "prepareSupportDefaultBehavior": 1, - "honorsChangeAnnotations": true - }, - "documentLink": { - "dynamicRegistration": true, - "tooltipSupport": true - }, - "typeDefinition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "implementation": { - "dynamicRegistration": true, - "linkSupport": true - }, - "colorProvider": { - "dynamicRegistration": true - }, - "foldingRange": { - "dynamicRegistration": true, - "rangeLimit": 5000, - "lineFoldingOnly": true, - "foldingRangeKind": { - "valueSet": [ - "comment", - "imports", - "region" - ] - }, - "foldingRange": { - "collapsedText": false - } - }, - "declaration": { - "dynamicRegistration": true, - "linkSupport": true - }, - "selectionRange": { - "dynamicRegistration": true - }, - "callHierarchy": { - "dynamicRegistration": true - }, - "semanticTokens": { - "dynamicRegistration": true, - "tokenTypes": [ - "namespace", - "type", - "class", - "enum", - "interface", - "struct", - "typeParameter", - "parameter", - "variable", - "property", - "enumMember", - "event", - "function", - "method", - "macro", - "keyword", - "modifier", - "comment", - "string", - "number", - "regexp", - "operator", - "decorator" - ], - "tokenModifiers": [ - "declaration", - "definition", - "readonly", - "static", - "deprecated", - "abstract", - "async", - "modification", - "documentation", - "defaultLibrary" - ], - "formats": [ - "relative" - ], - "requests": { - "range": true, - "full": { - "delta": true - } - }, - "multilineTokenSupport": false, - "overlappingTokenSupport": false, - "serverCancelSupport": true, - "augmentsSyntaxTokens": true - }, - "linkedEditingRange": { - "dynamicRegistration": true - }, - "typeHierarchy": { - "dynamicRegistration": true - }, - "inlineValue": { - "dynamicRegistration": true - }, - "inlayHint": { - "dynamicRegistration": true, - "resolveSupport": { - "properties": [ - "tooltip", - "textEdits", - "label.tooltip", - "label.location", - "label.command" - ] - } - }, - "diagnostic": { - "dynamicRegistration": true, - "relatedDocumentSupport": false - } - }, - "window": { - "showMessage": { - "messageActionItem": { - "additionalPropertiesSupport": true - } - }, - "showDocument": { - "support": true - }, - "workDoneProgress": true - }, - "general": { - "staleRequestSupport": { - "cancel": true, - "retryOnContentModified": [ - "textDocument/semanticTokens/full", - "textDocument/semanticTokens/range", - "textDocument/semanticTokens/full/delta" - ] - }, - "regularExpressions": { - "engine": "ECMAScript", - "version": "ES2020" - }, - "markdown": { - "parser": "marked", - "version": "1.1.0" - }, - "positionEncodings": [ - "utf-16" - ] - }, - "notebookDocument": { - "synchronization": { - "dynamicRegistration": true, - "executionSummarySupport": true - } - } - }, - "initializationOptions": { - "workspaceFolders": "[pathlib.Path(repository_absolute_path).as_uri()]", - "storagePath": null, - "codegen": { - "enabled": false - }, - "compiler": { - "jvm": { - "target": "default" - } - }, - "completion": { - "snippets": { - "enabled": true - } - }, - "diagnostics": { - "enabled": true, - "level": 4, - "debounceTime": 250 - }, - "scripts": { - "enabled": true, - "buildScriptsEnabled": true - }, - "indexing": { - "enabled": true - }, - "externalSources": { - "useKlsScheme": false, - "autoConvertToKotlin": false - }, - "inlayHints": { - "typeHints": false, - "parameterHints": false, - "chainedHints": false - }, - "formatting": { - "formatter": "ktfmt", - "ktfmt": { - "style": "google", - "indent": 4, - "maxWidth": 100, - "continuationIndent": 8, - "removeUnusedImports": true - } - } - }, - "trace": "verbose", - "workspaceFolders": "[\n {\n \"uri\": pathlib.Path(repository_absolute_path).as_uri(),\n \"name\": os.path.basename(repository_absolute_path),\n }\n ]" -} diff --git a/src/multilspy/language_servers/kotlin_language_server/kotlin_language_server.py b/src/multilspy/language_servers/kotlin_language_server/kotlin_language_server.py deleted file mode 100644 index ca4e8666..00000000 --- a/src/multilspy/language_servers/kotlin_language_server/kotlin_language_server.py +++ /dev/null @@ -1,227 +0,0 @@ -""" -Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin. -""" - -import asyncio -import dataclasses -import json -import logging -import os -import stat -import pathlib -from contextlib import asynccontextmanager -from typing import AsyncIterator - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils - - -@dataclasses.dataclass -class KotlinRuntimeDependencyPaths: - """ - Stores the paths to the runtime dependencies of Kotlin Language Server - """ - java_path: str - java_home_path: str - kotlin_executable_path: str - - -class KotlinLanguageServer(LanguageServer): - """ - Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin. - """ - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - 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) - self.runtime_dependency_paths = runtime_dependency_paths - - # Create command to execute the Kotlin Language Server script - cmd = f'"{self.runtime_dependency_paths.kotlin_executable_path}"' - - # Set environment variables including JAVA_HOME - 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", - ) - - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> KotlinRuntimeDependencyPaths: - """ - Setup runtime dependencies for Kotlin Language Server. - """ - platform_id = PlatformUtils.get_platform_id() - - # Verify platform support - assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), "Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment" - - # Load dependency information - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: - d = json.load(f) - del d["_description"] - - kotlin_dependency = d["runtimeDependency"] - java_dependency = d["java"][platform_id.value] - - # Setup paths for dependencies - static_dir = os.path.join(os.path.dirname(__file__), "static") - os.makedirs(static_dir, exist_ok=True) - - # Setup Java paths - java_dir = os.path.join(static_dir, "java") - os.makedirs(java_dir, exist_ok=True) - - java_home_path = os.path.join(java_dir, java_dependency["java_home_path"]) - java_path = os.path.join(java_dir, java_dependency["java_path"]) - - # 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"] - ) - # Make Java executable - if not platform_id.value.startswith("win-"): - os.chmod(java_path, 0o755) - - assert os.path.exists(java_path), f"Java executable not found at {java_path}" - - # Setup Kotlin Language Server paths - kotlin_ls_dir = os.path.join(static_dir, "server") - - # Get platform-specific executable script path - if platform_id.value.startswith("win-"): - kotlin_script = os.path.join(kotlin_ls_dir, "bin", "kotlin-language-server.bat") - else: - kotlin_script = os.path.join(kotlin_ls_dir, "bin", "kotlin-language-server") - - # 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"] - ) - - # Make script executable on Unix platforms - if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"): - os.chmod(kotlin_script, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) - - # 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) - else: - raise FileNotFoundError(f"Kotlin Language Server script not found at {kotlin_script}") - - return KotlinRuntimeDependencyPaths( - java_path=java_path, - java_home_path=java_home_path, - kotlin_executable_path=kotlin_executable_path - ) - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the Kotlin Language Server. - """ - with open(str(pathlib.PurePath(os.path.dirname(__file__), "initialize_params.json")), "r", encoding="utf-8") as f: - d: InitializeParams = json.load(f) - - del d["_description"] - - if not os.path.isabs(repository_absolute_path): - repository_absolute_path = os.path.abspath(repository_absolute_path) - - assert d["processId"] == "os.getpid()" - d["processId"] = os.getpid() - - assert d["rootPath"] == "repository_absolute_path" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "pathlib.Path(repository_absolute_path).as_uri()" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["initializationOptions"]["workspaceFolders"] == "[pathlib.Path(repository_absolute_path).as_uri()]" - d["initializationOptions"]["workspaceFolders"] = [pathlib.Path(repository_absolute_path).as_uri()] - - assert ( - d["workspaceFolders"] - == '[\n {\n "uri": pathlib.Path(repository_absolute_path).as_uri(),\n "name": os.path.basename(repository_absolute_path),\n }\n ]' - ) - d["workspaceFolders"] = [ - { - "uri": pathlib.Path(repository_absolute_path).as_uri(), - "name": os.path.basename(repository_absolute_path), - } - ] - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["KotlinLanguageServer"]: - """ - Starts the Kotlin Language Server, waits for the server to be ready and yields the LanguageServer instance. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and ready to serve requests - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown - ``` - """ - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - self.server.on_request("client/registerCapability", do_nothing) - self.server.on_notification("language/status", do_nothing) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - - async with super().start_server(): - self.logger.log("Starting Kotlin server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - - capabilities = init_response["capabilities"] - assert "textDocumentSync" in capabilities, "Server must support textDocumentSync" - assert "hoverProvider" in capabilities, "Server must support hover" - assert "completionProvider" in capabilities, "Server must support code completion" - assert "signatureHelpProvider" in capabilities, "Server must support signature help" - assert "definitionProvider" in capabilities, "Server must support go to definition" - assert "referencesProvider" in capabilities, "Server must support find references" - assert "documentSymbolProvider" in capabilities, "Server must support document symbols" - assert "workspaceSymbolProvider" in capabilities, "Server must support workspace symbols" - assert "semanticTokensProvider" in capabilities, "Server must support semantic tokens" - - self.server.notify.initialized({}) - self.completions_available.set() - - yield self diff --git a/src/multilspy/language_servers/kotlin_language_server/runtime_dependencies.json b/src/multilspy/language_servers/kotlin_language_server/runtime_dependencies.json deleted file mode 100644 index 3568c9d4..00000000 --- a/src/multilspy/language_servers/kotlin_language_server/runtime_dependencies.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "_description": "Used to download the runtime dependencies for Kotlin Language Server from https://github.com/fwcd/kotlin-language-server", - "runtimeDependency": { - "id": "KotlinLsp", - "description": "Kotlin Language Server", - "url": "https://github.com/fwcd/kotlin-language-server/releases/download/1.3.13/server.zip", - "archiveType": "zip" - }, - "java": { - "win-x64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix", - "archiveType": "zip", - "java_home_path": "extension/jre/21.0.7-win32-x86_64", - "java_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe" - }, - "linux-x64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix", - "archiveType": "zip", - "java_home_path": "extension/jre/21.0.7-linux-x86_64", - "java_path": "extension/jre/21.0.7-linux-x86_64/bin/java" - }, - "linux-arm64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix", - "archiveType": "zip", - "java_home_path": "extension/jre/21.0.7-linux-aarch64", - "java_path": "extension/jre/21.0.7-linux-aarch64/bin/java" - }, - "osx-x64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix", - "archiveType": "zip", - "java_home_path": "extension/jre/21.0.7-macosx-x86_64", - "java_path": "extension/jre/21.0.7-macosx-x86_64/bin/java" - }, - "osx-arm64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix", - "archiveType": "zip", - "java_home_path": "extension/jre/21.0.7-macosx-aarch64", - "java_path": "extension/jre/21.0.7-macosx-aarch64/bin/java" - } - } -} diff --git a/src/multilspy/language_servers/omnisharp/initialize_params.json b/src/multilspy/language_servers/omnisharp/initialize_params.json deleted file mode 100644 index f9ee86c3..00000000 --- a/src/multilspy/language_servers/omnisharp/initialize_params.json +++ /dev/null @@ -1,631 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "clientInfo": { - "name": "Visual Studio Code - Insiders", - "version": "1.82.0-insider" - }, - "locale": "en", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": { - "workspace": { - "applyEdit": true, - "workspaceEdit": { - "documentChanges": true, - "resourceOperations": [ - "create", - "rename", - "delete" - ], - "failureHandling": "textOnlyTransactional", - "normalizesLineEndings": true, - "changeAnnotationSupport": { - "groupsOnLabel": true - } - }, - "configuration": false, - "didChangeWatchedFiles": { - "dynamicRegistration": true, - "relativePatternSupport": true - }, - "symbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "resolveSupport": { - "properties": [ - "location.range" - ] - } - }, - "codeLens": { - "refreshSupport": true - }, - "executeCommand": { - "dynamicRegistration": true - }, - "didChangeConfiguration": { - "dynamicRegistration": true - }, - "workspaceFolders": true, - "semanticTokens": { - "refreshSupport": true - }, - "fileOperations": { - "dynamicRegistration": true, - "didCreate": true, - "didRename": true, - "didDelete": true, - "willCreate": true, - "willRename": true, - "willDelete": true - }, - "inlineValue": { - "refreshSupport": true - }, - "inlayHint": { - "refreshSupport": true - }, - "diagnostics": { - "refreshSupport": true - } - }, - "textDocument": { - "publishDiagnostics": { - "relatedInformation": true, - "versionSupport": false, - "tagSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "codeDescriptionSupport": true, - "dataSupport": true - }, - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - }, - "completion": { - "dynamicRegistration": true, - "contextSupport": true, - "completionItem": { - "snippetSupport": true, - "commitCharactersSupport": true, - "documentationFormat": [ - "markdown", - "plaintext" - ], - "deprecatedSupport": true, - "preselectSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "insertReplaceSupport": true, - "resolveSupport": { - "properties": [ - "documentation", - "detail", - "additionalTextEdits" - ] - }, - "insertTextModeSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "labelDetailsSupport": true - }, - "insertTextMode": 2, - "completionItemKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25 - ] - }, - "completionList": { - "itemDefaults": [ - "commitCharacters", - "editRange", - "insertTextFormat", - "insertTextMode" - ] - } - }, - "hover": { - "dynamicRegistration": true, - "contentFormat": [ - "markdown", - "plaintext" - ] - }, - "signatureHelp": { - "dynamicRegistration": true, - "signatureInformation": { - "documentationFormat": [ - "markdown", - "plaintext" - ], - "parameterInformation": { - "labelOffsetSupport": true - }, - "activeParameterSupport": true - }, - "contextSupport": true - }, - "definition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "references": { - "dynamicRegistration": true - }, - "documentHighlight": { - "dynamicRegistration": true - }, - "documentSymbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "hierarchicalDocumentSymbolSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "labelSupport": true - }, - "codeAction": { - "dynamicRegistration": true, - "isPreferredSupport": true, - "disabledSupport": true, - "dataSupport": true, - "resolveSupport": { - "properties": [ - "edit" - ] - }, - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "", - "quickfix", - "refactor", - "refactor.extract", - "refactor.inline", - "refactor.rewrite", - "source", - "source.organizeImports" - ] - } - }, - "honorsChangeAnnotations": false - }, - "codeLens": { - "dynamicRegistration": true - }, - "formatting": { - "dynamicRegistration": true - }, - "rangeFormatting": { - "dynamicRegistration": true - }, - "onTypeFormatting": { - "dynamicRegistration": true - }, - "rename": { - "dynamicRegistration": true, - "prepareSupport": true, - "prepareSupportDefaultBehavior": 1, - "honorsChangeAnnotations": true - }, - "documentLink": { - "dynamicRegistration": true, - "tooltipSupport": true - }, - "typeDefinition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "implementation": { - "dynamicRegistration": true, - "linkSupport": true - }, - "colorProvider": { - "dynamicRegistration": true - }, - "foldingRange": { - "dynamicRegistration": true, - "rangeLimit": 5000, - "lineFoldingOnly": true, - "foldingRangeKind": { - "valueSet": [ - "comment", - "imports", - "region" - ] - }, - "foldingRange": { - "collapsedText": false - } - }, - "declaration": { - "dynamicRegistration": true, - "linkSupport": true - }, - "selectionRange": { - "dynamicRegistration": true - }, - "callHierarchy": { - "dynamicRegistration": true - }, - "semanticTokens": { - "dynamicRegistration": true, - "tokenTypes": [ - "namespace", - "type", - "class", - "enum", - "interface", - "struct", - "typeParameter", - "parameter", - "variable", - "property", - "enumMember", - "event", - "function", - "method", - "macro", - "keyword", - "modifier", - "comment", - "string", - "number", - "regexp", - "operator", - "decorator" - ], - "tokenModifiers": [ - "declaration", - "definition", - "readonly", - "static", - "deprecated", - "abstract", - "async", - "modification", - "documentation", - "defaultLibrary" - ], - "formats": [ - "relative" - ], - "requests": { - "range": true, - "full": { - "delta": true - } - }, - "multilineTokenSupport": false, - "overlappingTokenSupport": false, - "serverCancelSupport": true, - "augmentsSyntaxTokens": false - }, - "linkedEditingRange": { - "dynamicRegistration": true - }, - "typeHierarchy": { - "dynamicRegistration": true - }, - "inlineValue": { - "dynamicRegistration": true - }, - "inlayHint": { - "dynamicRegistration": true, - "resolveSupport": { - "properties": [ - "tooltip", - "textEdits", - "label.tooltip", - "label.location", - "label.command" - ] - } - }, - "diagnostic": { - "dynamicRegistration": true, - "relatedDocumentSupport": false - } - }, - "window": { - "showMessage": { - "messageActionItem": { - "additionalPropertiesSupport": true - } - }, - "showDocument": { - "support": true - }, - "workDoneProgress": true - }, - "general": { - "staleRequestSupport": { - "cancel": true, - "retryOnContentModified": [ - "textDocument/semanticTokens/full", - "textDocument/semanticTokens/range", - "textDocument/semanticTokens/full/delta" - ] - }, - "regularExpressions": { - "engine": "ECMAScript", - "version": "ES2020" - }, - "markdown": { - "parser": "marked", - "version": "1.1.0", - "allowedTags": [ - "ul", - "li", - "p", - "code", - "blockquote", - "ol", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "hr", - "em", - "pre", - "table", - "thead", - "tbody", - "tr", - "th", - "td", - "div", - "del", - "a", - "strong", - "br", - "img", - "span" - ] - }, - "positionEncodings": [ - "utf-16" - ] - }, - "notebookDocument": { - "synchronization": { - "dynamicRegistration": true, - "executionSummarySupport": true - } - }, - "experimental": { - "snippetTextEdit": true, - "codeActionGroup": true, - "hoverActions": true, - "serverStatusNotification": true, - "colorDiagnosticOutput": true, - "openServerLogs": true, - "commands": { - "commands": [ - "editor.action.triggerParameterHints" - ] - } - } - }, - "initializationOptions": { - "RoslynExtensionsOptions": { - "EnableDecompilationSupport": false, - "EnableAnalyzersSupport": true, - "EnableImportCompletion": true, - "EnableAsyncCompletion": false, - "DocumentAnalysisTimeoutMs": 30000, - "DiagnosticWorkersThreadCount": 18, - "AnalyzeOpenDocumentsOnly": true, - "InlayHintsOptions": { - "EnableForParameters": false, - "ForLiteralParameters": false, - "ForIndexerParameters": false, - "ForObjectCreationParameters": false, - "ForOtherParameters": false, - "SuppressForParametersThatDifferOnlyBySuffix": false, - "SuppressForParametersThatMatchMethodIntent": false, - "SuppressForParametersThatMatchArgumentName": false, - "EnableForTypes": false, - "ForImplicitVariableTypes": false, - "ForLambdaParameterTypes": false, - "ForImplicitObjectCreation": false - }, - "LocationPaths": null - }, - "FormattingOptions": { - "OrganizeImports": false, - "EnableEditorConfigSupport": true, - "NewLine": "\n", - "UseTabs": false, - "TabSize": 4, - "IndentationSize": 4, - "SpacingAfterMethodDeclarationName": false, - "SeparateImportDirectiveGroups": false, - "SpaceWithinMethodDeclarationParenthesis": false, - "SpaceBetweenEmptyMethodDeclarationParentheses": false, - "SpaceAfterMethodCallName": false, - "SpaceWithinMethodCallParentheses": false, - "SpaceBetweenEmptyMethodCallParentheses": false, - "SpaceAfterControlFlowStatementKeyword": true, - "SpaceWithinExpressionParentheses": false, - "SpaceWithinCastParentheses": false, - "SpaceWithinOtherParentheses": false, - "SpaceAfterCast": false, - "SpaceBeforeOpenSquareBracket": false, - "SpaceBetweenEmptySquareBrackets": false, - "SpaceWithinSquareBrackets": false, - "SpaceAfterColonInBaseTypeDeclaration": true, - "SpaceAfterComma": true, - "SpaceAfterDot": false, - "SpaceAfterSemicolonsInForStatement": true, - "SpaceBeforeColonInBaseTypeDeclaration": true, - "SpaceBeforeComma": false, - "SpaceBeforeDot": false, - "SpaceBeforeSemicolonsInForStatement": false, - "SpacingAroundBinaryOperator": "single", - "IndentBraces": false, - "IndentBlock": true, - "IndentSwitchSection": true, - "IndentSwitchCaseSection": true, - "IndentSwitchCaseSectionWhenBlock": true, - "LabelPositioning": "oneLess", - "WrappingPreserveSingleLine": true, - "WrappingKeepStatementsOnSingleLine": true, - "NewLinesForBracesInTypes": true, - "NewLinesForBracesInMethods": true, - "NewLinesForBracesInProperties": true, - "NewLinesForBracesInAccessors": true, - "NewLinesForBracesInAnonymousMethods": true, - "NewLinesForBracesInControlBlocks": true, - "NewLinesForBracesInAnonymousTypes": true, - "NewLinesForBracesInObjectCollectionArrayInitializers": true, - "NewLinesForBracesInLambdaExpressionBody": true, - "NewLineForElse": true, - "NewLineForCatch": true, - "NewLineForFinally": true, - "NewLineForMembersInObjectInit": true, - "NewLineForMembersInAnonymousTypes": true, - "NewLineForClausesInQuery": true - }, - "FileOptions": { - "SystemExcludeSearchPatterns": [ - "**/node_modules/**/*", - "**/bin/**/*", - "**/obj/**/*", - "**/.git/**/*", - "**/.git", - "**/.svn", - "**/.hg", - "**/CVS", - "**/.DS_Store", - "**/Thumbs.db" - ], - "ExcludeSearchPatterns": [] - }, - "RenameOptions": { - "RenameOverloads": false, - "RenameInStrings": false, - "RenameInComments": false - }, - "ImplementTypeOptions": { - "InsertionBehavior": 0, - "PropertyGenerationBehavior": 0 - }, - "DotNetCliOptions": { - "LocationPaths": null - }, - "Plugins": { - "LocationPaths": null - } - }, - "trace": "verbose", - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/omnisharp/omnisharp.py b/src/multilspy/language_servers/omnisharp/omnisharp.py deleted file mode 100644 index df3cf0cd..00000000 --- a/src/multilspy/language_servers/omnisharp/omnisharp.py +++ /dev/null @@ -1,406 +0,0 @@ -""" -Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#. -""" - -import asyncio -import json -import logging -import os -import pathlib -import stat -from contextlib import asynccontextmanager -from typing import AsyncIterator, Iterable - -from overrides import override - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_exceptions import MultilspyException -from multilspy.multilspy_utils import FileUtils, PlatformUtils, PlatformId, DotnetVersion - - -def breadth_first_file_scan(root) -> Iterable[str]: - """ - This function was obtained from https://stackoverflow.com/questions/49654234/is-there-a-breadth-first-search-option-available-in-os-walk-or-equivalent-py - It traverses the directory tree in breadth first order. - """ - dirs = [root] - # while we has dirs to scan - while len(dirs): - next_dirs = [] - for parent in dirs: - # scan each dir - for f in os.listdir(parent): - # if there is a dir, then save for next ittr - # if it is a file then yield it (we'll return later) - ff = os.path.join(parent, f) - if os.path.isdir(ff): - next_dirs.append(ff) - else: - yield ff - - # once we've done all the current dirs then - # we set up the next itter as the child dirs - # from the current itter. - dirs = next_dirs - - -def find_least_depth_sln_file(root_dir) -> str | None: - for filename in breadth_first_file_scan(root_dir): - if filename.endswith(".sln"): - return filename - return None - - -class OmniSharp(LanguageServer): - """ - Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#. - """ - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - Creates an OmniSharp instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. - """ - omnisharp_executable_path, dll_path = self.setupRuntimeDependencies(logger, config) - - slnfilename = find_least_depth_sln_file(repository_root_path) - if slnfilename is None: - logger.log("No *.sln file found in repository", logging.ERROR) - raise MultilspyException("No SLN file found in repository") - - cmd = " ".join( - [ - omnisharp_executable_path, - "-lsp", - "--encoding", - "ascii", - "-z", - "-s", - f'"{slnfilename}"', - "--hostPID", - str(os.getpid()), - "DotNet:enablePackageRestore=false", - "--loglevel", - "trace", - "--plugin", - dll_path, - "FileOptions:SystemExcludeSearchPatterns:0=**/.git", - "FileOptions:SystemExcludeSearchPatterns:1=**/.svn", - "FileOptions:SystemExcludeSearchPatterns:2=**/.hg", - "FileOptions:SystemExcludeSearchPatterns:3=**/CVS", - "FileOptions:SystemExcludeSearchPatterns:4=**/.DS_Store", - "FileOptions:SystemExcludeSearchPatterns:5=**/Thumbs.db", - "RoslynExtensionsOptions:EnableAnalyzersSupport=true", - "FormattingOptions:EnableEditorConfigSupport=true", - "RoslynExtensionsOptions:EnableImportCompletion=true", - "Sdk:IncludePrereleases=true", - "RoslynExtensionsOptions:AnalyzeOpenDocumentsOnly=true", - "formattingOptions:useTabs=false", - "formattingOptions:tabSize=4", - "formattingOptions:indentationSize=4", - ] - ) - super().__init__( - config, logger, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp" - ) - - self.definition_available = asyncio.Event() - self.references_available = asyncio.Event() - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - return super().is_ignored_dirname(dirname) or dirname in ["bin", "obj"] - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the Omnisharp Language Server. - """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> tuple[str, str]: - """ - Setup runtime dependencies for OmniSharp. - """ - platform_id = PlatformUtils.get_platform_id() - dotnet_version = PlatformUtils.get_dotnet_version() - - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: - d = json.load(f) - del d["_description"] - - assert platform_id in [ - PlatformId.LINUX_x64, - PlatformId.WIN_x64, - ], "Only linux-x64 and win-x64 platform is supported for in multilspy at the moment" - assert dotnet_version in [ - DotnetVersion.V6, - DotnetVersion.V7, - DotnetVersion.V8 - ], "Only dotnet version 6 and 7 are supported in multilspy at the moment" - - # TODO: Do away with this assumption - # Currently, runtime binaries are not available for .Net 7 and .Net 8. Hence, we assume .Net 6 runtime binaries to be compatible with .Net 7, .Net 8 - if dotnet_version in [DotnetVersion.V7, DotnetVersion.V8]: - dotnet_version = DotnetVersion.V6 - - runtime_dependencies = d["runtimeDependencies"] - runtime_dependencies = [ - dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value - ] - runtime_dependencies = [ - dependency - for dependency in runtime_dependencies - if not ("dotnet_version" in dependency) or dependency["dotnet_version"] == dotnet_version.value - ] - assert len(runtime_dependencies) == 2 - runtime_dependencies = { - runtime_dependencies[0]["id"]: runtime_dependencies[0], - runtime_dependencies[1]["id"]: runtime_dependencies[1], - } - - assert "OmniSharp" in runtime_dependencies - assert "RazorOmnisharp" in runtime_dependencies - - omnisharp_ls_dir = os.path.join(os.path.dirname(__file__), "static", "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" - ) - 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, stat.S_IEXEC) - - razor_omnisharp_ls_dir = os.path.join(os.path.dirname(__file__), "static", "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" - ) - razor_omnisharp_dll_path = os.path.join( - razor_omnisharp_ls_dir, runtime_dependencies["RazorOmnisharp"]["dll_path"] - ) - assert os.path.exists(razor_omnisharp_dll_path) - - return omnisharp_executable_path, razor_omnisharp_dll_path - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["OmniSharp"]: - """ - Starts the Omnisharp Language Server, waits for the server to be ready and yields the LanguageServer instance. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and ready to serve requests - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown - """ - - async def register_capability_handler(params): - assert "registrations" in params - for registration in params["registrations"]: - if registration["method"] == "textDocument/definition": - self.definition_available.set() - if registration["method"] == "textDocument/references": - self.references_available.set() - if registration["method"] == "textDocument/completion": - self.completions_available.set() - - async def lang_status_handler(params): - # TODO: Should we wait for - # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} - # Before proceeding? - # if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": - # self.service_ready_event.set() - pass - - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def check_experimental_status(params): - if params["quiescent"] == True: - self.server_ready.set() - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - async def workspace_configuration_handler(params): - # TODO: We do not know the appropriate way to handle this request. Should ideally contact the OmniSharp dev team - return [ - { - "RoslynExtensionsOptions": { - "EnableDecompilationSupport": False, - "EnableAnalyzersSupport": True, - "EnableImportCompletion": True, - "EnableAsyncCompletion": False, - "DocumentAnalysisTimeoutMs": 30000, - "DiagnosticWorkersThreadCount": 18, - "AnalyzeOpenDocumentsOnly": True, - "InlayHintsOptions": { - "EnableForParameters": False, - "ForLiteralParameters": False, - "ForIndexerParameters": False, - "ForObjectCreationParameters": False, - "ForOtherParameters": False, - "SuppressForParametersThatDifferOnlyBySuffix": False, - "SuppressForParametersThatMatchMethodIntent": False, - "SuppressForParametersThatMatchArgumentName": False, - "EnableForTypes": False, - "ForImplicitVariableTypes": False, - "ForLambdaParameterTypes": False, - "ForImplicitObjectCreation": False, - }, - "LocationPaths": None, - }, - "FormattingOptions": { - "OrganizeImports": False, - "EnableEditorConfigSupport": True, - "NewLine": "\n", - "UseTabs": False, - "TabSize": 4, - "IndentationSize": 4, - "SpacingAfterMethodDeclarationName": False, - "SeparateImportDirectiveGroups": False, - "SpaceWithinMethodDeclarationParenthesis": False, - "SpaceBetweenEmptyMethodDeclarationParentheses": False, - "SpaceAfterMethodCallName": False, - "SpaceWithinMethodCallParentheses": False, - "SpaceBetweenEmptyMethodCallParentheses": False, - "SpaceAfterControlFlowStatementKeyword": True, - "SpaceWithinExpressionParentheses": False, - "SpaceWithinCastParentheses": False, - "SpaceWithinOtherParentheses": False, - "SpaceAfterCast": False, - "SpaceBeforeOpenSquareBracket": False, - "SpaceBetweenEmptySquareBrackets": False, - "SpaceWithinSquareBrackets": False, - "SpaceAfterColonInBaseTypeDeclaration": True, - "SpaceAfterComma": True, - "SpaceAfterDot": False, - "SpaceAfterSemicolonsInForStatement": True, - "SpaceBeforeColonInBaseTypeDeclaration": True, - "SpaceBeforeComma": False, - "SpaceBeforeDot": False, - "SpaceBeforeSemicolonsInForStatement": False, - "SpacingAroundBinaryOperator": "single", - "IndentBraces": False, - "IndentBlock": True, - "IndentSwitchSection": True, - "IndentSwitchCaseSection": True, - "IndentSwitchCaseSectionWhenBlock": True, - "LabelPositioning": "oneLess", - "WrappingPreserveSingleLine": True, - "WrappingKeepStatementsOnSingleLine": True, - "NewLinesForBracesInTypes": True, - "NewLinesForBracesInMethods": True, - "NewLinesForBracesInProperties": True, - "NewLinesForBracesInAccessors": True, - "NewLinesForBracesInAnonymousMethods": True, - "NewLinesForBracesInControlBlocks": True, - "NewLinesForBracesInAnonymousTypes": True, - "NewLinesForBracesInObjectCollectionArrayInitializers": True, - "NewLinesForBracesInLambdaExpressionBody": True, - "NewLineForElse": True, - "NewLineForCatch": True, - "NewLineForFinally": True, - "NewLineForMembersInObjectInit": True, - "NewLineForMembersInAnonymousTypes": True, - "NewLineForClausesInQuery": True, - }, - "FileOptions": { - "SystemExcludeSearchPatterns": [ - "**/node_modules/**/*", - "**/bin/**/*", - "**/obj/**/*", - "**/.git/**/*", - "**/.git", - "**/.svn", - "**/.hg", - "**/CVS", - "**/.DS_Store", - "**/Thumbs.db", - ], - "ExcludeSearchPatterns": [], - }, - "RenameOptions": { - "RenameOverloads": False, - "RenameInStrings": False, - "RenameInComments": False, - }, - "ImplementTypeOptions": { - "InsertionBehavior": 0, - "PropertyGenerationBehavior": 0, - }, - "DotNetCliOptions": {"LocationPaths": None}, - "Plugins": {"LocationPaths": None}, - } - ] - - self.server.on_request("client/registerCapability", register_capability_handler) - self.server.on_notification("language/status", lang_status_handler) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - self.server.on_notification("experimental/serverStatus", check_experimental_status) - self.server.on_request("workspace/configuration", workspace_configuration_handler) - - async with super().start_server(): - self.logger.log("Starting OmniSharp server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - self.server.notify.initialized({}) - with open(os.path.join(os.path.dirname(__file__), "workspace_did_change_configuration.json"), "r", encoding="utf-8") as f: - self.server.notify.workspace_did_change_configuration({ - "settings": json.load(f) - }) - assert "capabilities" in init_response - if ( - "definitionProvider" in init_response["capabilities"] - and init_response["capabilities"]["definitionProvider"] - ): - self.definition_available.set() - if ( - "referencesProvider" in init_response["capabilities"] - and init_response["capabilities"]["referencesProvider"] - ): - self.references_available.set() - - await self.definition_available.wait() - await self.references_available.wait() - - yield self diff --git a/src/multilspy/language_servers/omnisharp/runtime_dependencies.json b/src/multilspy/language_servers/omnisharp/runtime_dependencies.json deleted file mode 100644 index 7dfa1fd4..00000000 --- a/src/multilspy/language_servers/omnisharp/runtime_dependencies.json +++ /dev/null @@ -1,441 +0,0 @@ -{ - "_description": "Used to download the runtime dependencies for running OmniSharp. Obtained from https://github.com/dotnet/vscode-csharp/blob/main/package.json", - "runtimeDependencies": [ - { - "id": "OmniSharp", - "description": "OmniSharp for Windows (.NET 4 / x86)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-x86-1.39.10.zip", - "installPath": ".omnisharp/1.39.10", - "platforms": [ - "win32" - ], - "architectures": [ - "x86" - ], - "installTestPath": "./.omnisharp/1.39.10/OmniSharp.exe", - "platformId": "win-x86", - "isFramework": true, - "integrity": "C81CE2099AD494EF63F9D88FAA70D55A68CF175810F944526FF94AAC7A5109F9", - "dotnet_version": "4", - "binaryName": "OmniSharp.exe" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Windows (.NET 6 / x86)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-x86-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "win32" - ], - "architectures": [ - "x86" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "win-x86", - "isFramework": false, - "integrity": "B7E62415CFC3DAC2154AC636C5BF0FB4B2C9BBF11B5A1FBF72381DDDED59791E", - "dotnet_version": "6", - "binaryName": "OmniSharp.exe" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Windows (.NET 4 / x64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-x64-1.39.10.zip", - "installPath": ".omnisharp/1.39.10", - "platforms": [ - "win32" - ], - "architectures": [ - "x86_64" - ], - "installTestPath": "./.omnisharp/1.39.10/OmniSharp.exe", - "platformId": "win-x64", - "isFramework": true, - "integrity": "BE0ED10AACEA17E14B78BD0D887DE5935D4ECA3712192A701F3F2100CA3C8B6E", - "dotnet_version": "4", - "binaryName": "OmniSharp.exe" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Windows (.NET 6 / x64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-x64-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "win32" - ], - "architectures": [ - "x86_64" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "win-x64", - "isFramework": false, - "integrity": "A73327395E7EF92C1D8E307055463DA412662C03F077ECC743462FD2760BB537", - "dotnet_version": "6", - "binaryName": "OmniSharp.exe" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Windows (.NET 4 / arm64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-arm64-1.39.10.zip", - "installPath": ".omnisharp/1.39.10", - "platforms": [ - "win32" - ], - "architectures": [ - "arm64" - ], - "installTestPath": "./.omnisharp/1.39.10/OmniSharp.exe", - "platformId": "win-arm64", - "isFramework": true, - "integrity": "32FA0067B0639F87760CD1A769B16E6A53588C137C4D31661836CA4FB28D3DD6", - "dotnet_version": "4", - "binaryName": "OmniSharp.exe" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Windows (.NET 6 / arm64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-arm64-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "win32" - ], - "architectures": [ - "arm64" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "win-arm64", - "isFramework": false, - "integrity": "433F9B360CAA7B4DDD85C604D5C5542C1A718BCF2E71B2BCFC7526E6D41F4E8F", - "dotnet_version": "6", - "binaryName": "OmniSharp.exe" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for OSX (Mono / x64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-osx-1.39.10.zip", - "installPath": ".omnisharp/1.39.10", - "platforms": [ - "darwin" - ], - "architectures": [ - "x86_64", - "arm64" - ], - "binaries": [ - "./mono.osx", - "./run" - ], - "installTestPath": "./.omnisharp/1.39.10/run", - "platformId": "osx", - "isFramework": true, - "integrity": "2CC42F0EC7C30CFA8858501D12ECB6FB685A1FCFB8ECB35698A4B12406551968", - "dotnet_version": "mono" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for OSX (.NET 6 / x64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-osx-x64-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "darwin" - ], - "architectures": [ - "x86_64" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "osx-x64", - "isFramework": false, - "integrity": "C9D6E9F2C839A66A7283AE6A9EC545EE049B48EB230D33E91A6322CB67FF9D97", - "dotnet_version": "6" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for OSX (.NET 6 / arm64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-osx-arm64-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "darwin" - ], - "architectures": [ - "arm64" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "osx-arm64", - "isFramework": false, - "integrity": "851350F52F83E3BAD5A92D113E4B9882FCD1DEB16AA84FF94B6F2CEE3C70051E", - "dotnet_version": "6" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Linux (Mono / x86)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-x86-1.39.10.zip", - "installPath": ".omnisharp/1.39.10", - "platforms": [ - "linux" - ], - "architectures": [ - "x86", - "i686" - ], - "binaries": [ - "./mono.linux-x86", - "./run" - ], - "installTestPath": "./.omnisharp/1.39.10/run", - "platformId": "linux-x86", - "isFramework": true, - "integrity": "474B1CDBAE64CFEC655FB6B0659BCE481023C48274441C72991E67B6E13E56A1", - "dotnet_version": "mono" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Linux (Mono / x64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-x64-1.39.10.zip", - "installPath": ".omnisharp/1.39.10", - "platforms": [ - "linux" - ], - "architectures": [ - "x86_64" - ], - "binaries": [ - "./mono.linux-x86_64", - "./run" - ], - "installTestPath": "./.omnisharp/1.39.10/run", - "platformId": "linux-x64", - "isFramework": true, - "integrity": "FB4CAA47343265100349375D79DBCCE1868950CED675CB07FCBE8462EDBCDD37", - "dotnet_version": "mono" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Linux (.NET 6 / x64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-x64-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "linux" - ], - "architectures": [ - "x86_64" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "linux-x64", - "isFramework": false, - "integrity": "0926D3BEA060BF4373356B2FC0A68C10D0DE1B1150100B551BA5932814CE51E2", - "dotnet_version": "6", - "binaryName": "OmniSharp" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Linux (Mono / arm64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-arm64-1.39.10.zip", - "installPath": ".omnisharp/1.39.10", - "platforms": [ - "linux" - ], - "architectures": [ - "arm64" - ], - "binaries": [ - "./mono.linux-arm64", - "./run" - ], - "installTestPath": "./.omnisharp/1.39.10/run", - "platformId": "linux-arm64", - "isFramework": true, - "integrity": "478F3594DFD0167E9A56E36F0364A86C73F8132A3E7EA916CA1419EFE141D2CC", - "dotnet_version": "mono" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Linux (.NET 6 / arm64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-arm64-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "linux" - ], - "architectures": [ - "arm64" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "linux-arm64", - "isFramework": false, - "integrity": "6FB6A572043A74220A92F6C19C7BB0C3743321C7563A815FD2702EF4FA7D688E", - "dotnet_version": "6" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Linux musl (.NET 6 / x64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-musl-x64-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "linux-musl" - ], - "architectures": [ - "x86_64" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "linux-musl-x64", - "isFramework": false, - "integrity": "6BFDA3AD11DBB0C6514B86ECC3E1597CC41C6E309B7575F7C599E07D9E2AE610", - "dotnet_version": "6" - }, - { - "id": "OmniSharp", - "description": "OmniSharp for Linux musl (.NET 6 / arm64)", - "url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-musl-arm64-net6.0-1.39.10.zip", - "installPath": ".omnisharp/1.39.10-net6.0", - "platforms": [ - "linux-musl" - ], - "architectures": [ - "arm64" - ], - "installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll", - "platformId": "linux-musl-arm64", - "isFramework": false, - "integrity": "DA63619EA024EB9BBF6DB5A85C6150CAB5C0BD554544A3596ED1B17F926D6875", - "dotnet_version": "6" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (Windows / x64)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/8d42e62ea4051381c219b3e31bc4eced/razorlanguageserver-win-x64-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "win32" - ], - "architectures": [ - "x86_64" - ], - "platformId": "win-x64", - "dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (Windows / x86)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/e440c4f3a4a96334fe177513935fa010/razorlanguageserver-win-x86-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "win32" - ], - "architectures": [ - "x86" - ], - "platformId": "win-x86", - "dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (Windows / ARM64)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/4ef26e45cf32fe8d51c0e7dd21f1fef6/razorlanguageserver-win-arm64-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "win32" - ], - "architectures": [ - "arm64" - ], - "platformId": "win-arm64", - "dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (Linux / x64)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/6d4e23a3c7cf0465743950a39515a716/razorlanguageserver-linux-x64-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "linux" - ], - "architectures": [ - "x86_64" - ], - "binaries": [ - "./rzls" - ], - "platformId": "linux-x64", - "dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (Linux ARM64)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/85deebd44647ebf65724cc291d722283/razorlanguageserver-linux-arm64-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "linux" - ], - "architectures": [ - "arm64" - ], - "binaries": [ - "./rzls" - ], - "platformId": "linux-arm64" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (Linux musl / x64)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/4f0caa94ae182785655efb15eafcef23/razorlanguageserver-linux-musl-x64-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "linux-musl" - ], - "architectures": [ - "x86_64" - ], - "binaries": [ - "./rzls" - ], - "platformId": "linux-musl-x64" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (Linux musl ARM64)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/0a24828206a6f3b4bc743d058ef88ce7/razorlanguageserver-linux-musl-arm64-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "linux-musl" - ], - "architectures": [ - "arm64" - ], - "binaries": [ - "./rzls" - ], - "platformId": "linux-musl-arm64" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (macOS / x64)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/2afcafaf41082989efcc10405abb9314/razorlanguageserver-osx-x64-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "darwin" - ], - "architectures": [ - "x86_64" - ], - "binaries": [ - "./rzls" - ], - "platformId": "osx-x64" - }, - { - "id": "RazorOmnisharp", - "description": "Razor Language Server for OmniSharp (macOS ARM64)", - "url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/8bf2ed2f00d481a5987e3eb5165afddd/razorlanguageserver-osx-arm64-7.0.0-preview.23363.1.zip", - "installPath": ".razoromnisharp", - "platforms": [ - "darwin" - ], - "architectures": [ - "arm64" - ], - "binaries": [ - "./rzls" - ], - "platformId": "osx-arm64" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/omnisharp/workspace_did_change_configuration.json b/src/multilspy/language_servers/omnisharp/workspace_did_change_configuration.json deleted file mode 100644 index 148841c3..00000000 --- a/src/multilspy/language_servers/omnisharp/workspace_did_change_configuration.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "RoslynExtensionsOptions": { - "EnableDecompilationSupport": false, - "EnableAnalyzersSupport": true, - "EnableImportCompletion": true, - "EnableAsyncCompletion": false, - "DocumentAnalysisTimeoutMs": 30000, - "DiagnosticWorkersThreadCount": 18, - "AnalyzeOpenDocumentsOnly": true, - "InlayHintsOptions": { - "EnableForParameters": false, - "ForLiteralParameters": false, - "ForIndexerParameters": false, - "ForObjectCreationParameters": false, - "ForOtherParameters": false, - "SuppressForParametersThatDifferOnlyBySuffix": false, - "SuppressForParametersThatMatchMethodIntent": false, - "SuppressForParametersThatMatchArgumentName": false, - "EnableForTypes": false, - "ForImplicitVariableTypes": false, - "ForLambdaParameterTypes": false, - "ForImplicitObjectCreation": false - }, - "LocationPaths": null - }, - "FormattingOptions": { - "OrganizeImports": false, - "EnableEditorConfigSupport": true, - "NewLine": "\n", - "UseTabs": false, - "TabSize": 4, - "IndentationSize": 4, - "SpacingAfterMethodDeclarationName": false, - "SeparateImportDirectiveGroups": false, - "SpaceWithinMethodDeclarationParenthesis": false, - "SpaceBetweenEmptyMethodDeclarationParentheses": false, - "SpaceAfterMethodCallName": false, - "SpaceWithinMethodCallParentheses": false, - "SpaceBetweenEmptyMethodCallParentheses": false, - "SpaceAfterControlFlowStatementKeyword": true, - "SpaceWithinExpressionParentheses": false, - "SpaceWithinCastParentheses": false, - "SpaceWithinOtherParentheses": false, - "SpaceAfterCast": false, - "SpaceBeforeOpenSquareBracket": false, - "SpaceBetweenEmptySquareBrackets": false, - "SpaceWithinSquareBrackets": false, - "SpaceAfterColonInBaseTypeDeclaration": true, - "SpaceAfterComma": true, - "SpaceAfterDot": false, - "SpaceAfterSemicolonsInForStatement": true, - "SpaceBeforeColonInBaseTypeDeclaration": true, - "SpaceBeforeComma": false, - "SpaceBeforeDot": false, - "SpaceBeforeSemicolonsInForStatement": false, - "SpacingAroundBinaryOperator": "single", - "IndentBraces": false, - "IndentBlock": true, - "IndentSwitchSection": true, - "IndentSwitchCaseSection": true, - "IndentSwitchCaseSectionWhenBlock": true, - "LabelPositioning": "oneLess", - "WrappingPreserveSingleLine": true, - "WrappingKeepStatementsOnSingleLine": true, - "NewLinesForBracesInTypes": true, - "NewLinesForBracesInMethods": true, - "NewLinesForBracesInProperties": true, - "NewLinesForBracesInAccessors": true, - "NewLinesForBracesInAnonymousMethods": true, - "NewLinesForBracesInControlBlocks": true, - "NewLinesForBracesInAnonymousTypes": true, - "NewLinesForBracesInObjectCollectionArrayInitializers": true, - "NewLinesForBracesInLambdaExpressionBody": true, - "NewLineForElse": true, - "NewLineForCatch": true, - "NewLineForFinally": true, - "NewLineForMembersInObjectInit": true, - "NewLineForMembersInAnonymousTypes": true, - "NewLineForClausesInQuery": true - }, - "FileOptions": { - "SystemExcludeSearchPatterns": [ - "**/node_modules/**/*", - "**/bin/**/*", - "**/obj/**/*", - "**/.git/**/*", - "**/.git", - "**/.svn", - "**/.hg", - "**/CVS", - "**/.DS_Store", - "**/Thumbs.db" - ], - "ExcludeSearchPatterns": [] - }, - "RenameOptions": { - "RenameOverloads": false, - "RenameInStrings": false, - "RenameInComments": false - }, - "ImplementTypeOptions": { - "InsertionBehavior": 0, - "PropertyGenerationBehavior": 0 - }, - "DotNetCliOptions": { - "LocationPaths": null - }, - "Plugins": { - "LocationPaths": null - } -} \ No newline at end of file diff --git a/src/multilspy/language_servers/pyright_language_server/pyright_server.py b/src/multilspy/language_servers/pyright_language_server/pyright_server.py deleted file mode 100644 index 48855831..00000000 --- a/src/multilspy/language_servers/pyright_language_server/pyright_server.py +++ /dev/null @@ -1,246 +0,0 @@ -""" -Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python. -""" - -import asyncio -import json -import logging -import os -import pathlib -import re -from contextlib import asynccontextmanager -from typing import AsyncIterator, Tuple - -from overrides import override - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig - - -class PyrightServer(LanguageServer): - """ - Provides Python specific instantiation of the LanguageServer class using Pyright. - Contains various configurations and settings specific to Python. - """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - 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", - ) - - # Event to signal when initial workspace analysis is complete - self.analysis_complete = asyncio.Event() - self.found_source_files = False - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"] - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the Pyright Language Server. - """ - # Create basic initialization parameters - initialize_params: InitializeParams = { # type: ignore - "processId": os.getpid(), - "rootPath": repository_absolute_path, - "rootUri": pathlib.Path(repository_absolute_path).as_uri(), - "initializationOptions": { - "exclude": [ - "**/__pycache__", - "**/.venv", - "**/.env", - "**/build", - "**/dist", - "**/.pixi", - ], - "reportMissingImports": "error", - }, - "capabilities": { - "workspace": { - "applyEdit": True, - "workspaceEdit": {"documentChanges": True}, - "didChangeConfiguration": {"dynamicRegistration": True}, - "didChangeWatchedFiles": {"dynamicRegistration": True}, - "symbol": { - "dynamicRegistration": True, - "symbolKind": { - "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26] - }, - }, - "executeCommand": {"dynamicRegistration": True}, - }, - "textDocument": { - "synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True}, - "completion": { - "dynamicRegistration": True, - "contextSupport": True, - "completionItem": { - "snippetSupport": True, - "commitCharactersSupport": True, - "documentationFormat": ["markdown", "plaintext"], - "deprecatedSupport": True, - "preselectSupport": True, - }, - "completionItemKind": { - "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] - }, - }, - "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]}, - "signatureHelp": { - "dynamicRegistration": True, - "signatureInformation": { - "documentationFormat": ["markdown", "plaintext"], - "parameterInformation": {"labelOffsetSupport": True}, - }, - }, - "definition": {"dynamicRegistration": True}, - "references": {"dynamicRegistration": True}, - "documentHighlight": {"dynamicRegistration": True}, - "documentSymbol": { - "dynamicRegistration": True, - "symbolKind": { - "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26] - }, - "hierarchicalDocumentSymbolSupport": True, - }, - "codeAction": { - "dynamicRegistration": True, - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "", - "quickfix", - "refactor", - "refactor.extract", - "refactor.inline", - "refactor.rewrite", - "source", - "source.organizeImports", - ] - } - }, - }, - "codeLens": {"dynamicRegistration": True}, - "formatting": {"dynamicRegistration": True}, - "rangeFormatting": {"dynamicRegistration": True}, - "onTypeFormatting": {"dynamicRegistration": True}, - "rename": {"dynamicRegistration": True}, - "publishDiagnostics": {"relatedInformation": True}, - }, - }, - "workspaceFolders": [ - {"uri": pathlib.Path(repository_absolute_path).as_uri(), "name": os.path.basename(repository_absolute_path)} - ], - } - - return initialize_params - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["PyrightServer"]: - """ - Starts the Pyright Language Server and waits for initial workspace analysis to complete. - - This prevents zombie processes by ensuring Pyright has finished its initial background - tasks before we consider the server ready. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and workspace analysis is complete - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown cleanly - ``` - """ - - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def window_log_message(msg): - """ - Monitor Pyright's log messages to detect when initial analysis is complete. - 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) - - # 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) - self.found_source_files = True - self.analysis_complete.set() - self.completions_available.set() - - async def check_experimental_status(params): - """ - 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) - if not self.found_source_files: - self.analysis_complete.set() - self.completions_available.set() - - # Set up notification handlers - self.server.on_request("client/registerCapability", do_nothing) - self.server.on_notification("language/status", do_nothing) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - self.server.on_notification("experimental/serverStatus", check_experimental_status) - - async with super().start_server(): - self.logger.log("Starting pyright-langserver server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - self.logger.log(f"Received initialize response from pyright server: {init_response}", logging.INFO) - - # Verify that the server supports our required features - assert "textDocumentSync" in init_response["capabilities"] - assert "completionProvider" in init_response["capabilities"] - assert "definitionProvider" in init_response["capabilities"] - - # Complete the initialization handshake - self.server.notify.initialized({}) - - # 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) - try: - await asyncio.wait_for(self.analysis_complete.wait(), timeout=1.0) - self.logger.log("Pyright initial analysis complete, server ready", logging.INFO) - except asyncio.TimeoutError: - self.logger.log("Timeout waiting for Pyright analysis completion, proceeding anyway", logging.WARNING) - # Fallback: assume analysis is complete after timeout - self.analysis_complete.set() - self.completions_available.set() - - yield self \ No newline at end of file diff --git a/src/multilspy/language_servers/rust_analyzer/initialize_params.json b/src/multilspy/language_servers/rust_analyzer/initialize_params.json deleted file mode 100644 index 00368eed..00000000 --- a/src/multilspy/language_servers/rust_analyzer/initialize_params.json +++ /dev/null @@ -1,917 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "clientInfo": { - "name": "Visual Studio Code - Insiders", - "version": "1.82.0-insider" - }, - "locale": "en", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": { - "workspace": { - "applyEdit": true, - "workspaceEdit": { - "documentChanges": true, - "resourceOperations": [ - "create", - "rename", - "delete" - ], - "failureHandling": "textOnlyTransactional", - "normalizesLineEndings": true, - "changeAnnotationSupport": { - "groupsOnLabel": true - } - }, - "configuration": true, - "didChangeWatchedFiles": { - "dynamicRegistration": true, - "relativePatternSupport": true - }, - "symbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "resolveSupport": { - "properties": [ - "location.range" - ] - } - }, - "codeLens": { - "refreshSupport": true - }, - "executeCommand": { - "dynamicRegistration": true - }, - "didChangeConfiguration": { - "dynamicRegistration": true - }, - "workspaceFolders": true, - "semanticTokens": { - "refreshSupport": true - }, - "fileOperations": { - "dynamicRegistration": true, - "didCreate": true, - "didRename": true, - "didDelete": true, - "willCreate": true, - "willRename": true, - "willDelete": true - }, - "inlineValue": { - "refreshSupport": true - }, - "inlayHint": { - "refreshSupport": true - }, - "diagnostics": { - "refreshSupport": true - } - }, - "textDocument": { - "publishDiagnostics": { - "relatedInformation": true, - "versionSupport": false, - "tagSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "codeDescriptionSupport": true, - "dataSupport": true - }, - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - }, - "completion": { - "dynamicRegistration": true, - "contextSupport": true, - "completionItem": { - "snippetSupport": true, - "commitCharactersSupport": true, - "documentationFormat": [ - "markdown", - "plaintext" - ], - "deprecatedSupport": true, - "preselectSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "insertReplaceSupport": true, - "resolveSupport": { - "properties": [ - "documentation", - "detail", - "additionalTextEdits" - ] - }, - "insertTextModeSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "labelDetailsSupport": true - }, - "insertTextMode": 2, - "completionItemKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25 - ] - }, - "completionList": { - "itemDefaults": [ - "commitCharacters", - "editRange", - "insertTextFormat", - "insertTextMode" - ] - } - }, - "hover": { - "dynamicRegistration": true, - "contentFormat": [ - "markdown", - "plaintext" - ] - }, - "signatureHelp": { - "dynamicRegistration": true, - "signatureInformation": { - "documentationFormat": [ - "markdown", - "plaintext" - ], - "parameterInformation": { - "labelOffsetSupport": true - }, - "activeParameterSupport": true - }, - "contextSupport": true - }, - "definition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "references": { - "dynamicRegistration": true - }, - "documentHighlight": { - "dynamicRegistration": true - }, - "documentSymbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "hierarchicalDocumentSymbolSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "labelSupport": true - }, - "codeAction": { - "dynamicRegistration": true, - "isPreferredSupport": true, - "disabledSupport": true, - "dataSupport": true, - "resolveSupport": { - "properties": [ - "edit" - ] - }, - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "", - "quickfix", - "refactor", - "refactor.extract", - "refactor.inline", - "refactor.rewrite", - "source", - "source.organizeImports" - ] - } - }, - "honorsChangeAnnotations": false - }, - "codeLens": { - "dynamicRegistration": true - }, - "formatting": { - "dynamicRegistration": true - }, - "rangeFormatting": { - "dynamicRegistration": true - }, - "onTypeFormatting": { - "dynamicRegistration": true - }, - "rename": { - "dynamicRegistration": true, - "prepareSupport": true, - "prepareSupportDefaultBehavior": 1, - "honorsChangeAnnotations": true - }, - "documentLink": { - "dynamicRegistration": true, - "tooltipSupport": true - }, - "typeDefinition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "implementation": { - "dynamicRegistration": true, - "linkSupport": true - }, - "colorProvider": { - "dynamicRegistration": true - }, - "foldingRange": { - "dynamicRegistration": true, - "rangeLimit": 5000, - "lineFoldingOnly": true, - "foldingRangeKind": { - "valueSet": [ - "comment", - "imports", - "region" - ] - }, - "foldingRange": { - "collapsedText": false - } - }, - "declaration": { - "dynamicRegistration": true, - "linkSupport": true - }, - "selectionRange": { - "dynamicRegistration": true - }, - "callHierarchy": { - "dynamicRegistration": true - }, - "semanticTokens": { - "dynamicRegistration": true, - "tokenTypes": [ - "namespace", - "type", - "class", - "enum", - "interface", - "struct", - "typeParameter", - "parameter", - "variable", - "property", - "enumMember", - "event", - "function", - "method", - "macro", - "keyword", - "modifier", - "comment", - "string", - "number", - "regexp", - "operator", - "decorator" - ], - "tokenModifiers": [ - "declaration", - "definition", - "readonly", - "static", - "deprecated", - "abstract", - "async", - "modification", - "documentation", - "defaultLibrary" - ], - "formats": [ - "relative" - ], - "requests": { - "range": true, - "full": { - "delta": true - } - }, - "multilineTokenSupport": false, - "overlappingTokenSupport": false, - "serverCancelSupport": true, - "augmentsSyntaxTokens": false - }, - "linkedEditingRange": { - "dynamicRegistration": true - }, - "typeHierarchy": { - "dynamicRegistration": true - }, - "inlineValue": { - "dynamicRegistration": true - }, - "inlayHint": { - "dynamicRegistration": true, - "resolveSupport": { - "properties": [ - "tooltip", - "textEdits", - "label.tooltip", - "label.location", - "label.command" - ] - } - }, - "diagnostic": { - "dynamicRegistration": true, - "relatedDocumentSupport": false - } - }, - "window": { - "showMessage": { - "messageActionItem": { - "additionalPropertiesSupport": true - } - }, - "showDocument": { - "support": true - }, - "workDoneProgress": true - }, - "general": { - "staleRequestSupport": { - "cancel": true, - "retryOnContentModified": [ - "textDocument/semanticTokens/full", - "textDocument/semanticTokens/range", - "textDocument/semanticTokens/full/delta" - ] - }, - "regularExpressions": { - "engine": "ECMAScript", - "version": "ES2020" - }, - "markdown": { - "parser": "marked", - "version": "1.1.0", - "allowedTags": [ - "ul", - "li", - "p", - "code", - "blockquote", - "ol", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "hr", - "em", - "pre", - "table", - "thead", - "tbody", - "tr", - "th", - "td", - "div", - "del", - "a", - "strong", - "br", - "img", - "span" - ] - }, - "positionEncodings": [ - "utf-16" - ] - }, - "notebookDocument": { - "synchronization": { - "dynamicRegistration": true, - "executionSummarySupport": true - } - }, - "experimental": { - "snippetTextEdit": true, - "codeActionGroup": true, - "hoverActions": true, - "serverStatusNotification": true, - "colorDiagnosticOutput": true, - "openServerLogs": true, - "localDocs": true, - "commands": { - "commands": [ - "rust-analyzer.runSingle", - "rust-analyzer.debugSingle", - "rust-analyzer.showReferences", - "rust-analyzer.gotoLocation", - "editor.action.triggerParameterHints" - ] - } - } - }, - "initializationOptions": { - "cargoRunner": null, - "runnables": { - "extraEnv": null, - "problemMatcher": [ - "$rustc" - ], - "command": null, - "extraArgs": [] - }, - "statusBar": { - "clickAction": "openLogs" - }, - "server": { - "path": null, - "extraEnv": null - }, - "trace": { - "server": "verbose", - "extension": false - }, - "debug": { - "engine": "auto", - "sourceFileMap": { - "/rustc/": "${env:USERPROFILE}/.rustup/toolchains//lib/rustlib/src/rust" - }, - "openDebugPane": false, - "engineSettings": {} - }, - "restartServerOnConfigChange": false, - "typing": { - "continueCommentsOnNewline": true, - "autoClosingAngleBrackets": { - "enable": false - } - }, - "diagnostics": { - "previewRustcOutput": false, - "useRustcErrorCode": false, - "disabled": [], - "enable": true, - "experimental": { - "enable": false - }, - "remapPrefix": {}, - "warningsAsHint": [], - "warningsAsInfo": [] - }, - "discoverProjectRunner": null, - "showUnlinkedFileNotification": true, - "showDependenciesExplorer": true, - "assist": { - "emitMustUse": false, - "expressionFillDefault": "todo" - }, - "cachePriming": { - "enable": true, - "numThreads": 0 - }, - "cargo": { - "autoreload": true, - "buildScripts": { - "enable": true, - "invocationLocation": "workspace", - "invocationStrategy": "per_workspace", - "overrideCommand": null, - "useRustcWrapper": true - }, - "cfgs": {}, - "extraArgs": [], - "extraEnv": {}, - "features": [], - "noDefaultFeatures": false, - "sysroot": "discover", - "sysrootSrc": null, - "target": null, - "unsetTest": [ - "core" - ] - }, - "checkOnSave": true, - "check": { - "allTargets": true, - "command": "check", - "extraArgs": [], - "extraEnv": {}, - "features": null, - "ignore": [], - "invocationLocation": "workspace", - "invocationStrategy": "per_workspace", - "noDefaultFeatures": null, - "overrideCommand": null, - "targets": null - }, - "completion": { - "autoimport": { - "enable": true - }, - "autoself": { - "enable": true - }, - "callable": { - "snippets": "fill_arguments" - }, - "fullFunctionSignatures": { - "enable": false - }, - "limit": null, - "postfix": { - "enable": true - }, - "privateEditable": { - "enable": false - }, - "snippets": { - "custom": { - "Arc::new": { - "postfix": "arc", - "body": "Arc::new(${receiver})", - "requires": "std::sync::Arc", - "description": "Put the expression into an `Arc`", - "scope": "expr" - }, - "Rc::new": { - "postfix": "rc", - "body": "Rc::new(${receiver})", - "requires": "std::rc::Rc", - "description": "Put the expression into an `Rc`", - "scope": "expr" - }, - "Box::pin": { - "postfix": "pinbox", - "body": "Box::pin(${receiver})", - "requires": "std::boxed::Box", - "description": "Put the expression into a pinned `Box`", - "scope": "expr" - }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, - "Err": { - "postfix": "err", - "body": "Err(${receiver})", - "description": "Wrap the expression in a `Result::Err`", - "scope": "expr" - }, - "Some": { - "postfix": "some", - "body": "Some(${receiver})", - "description": "Wrap the expression in an `Option::Some`", - "scope": "expr" - } - } - } - }, - "files": { - "excludeDirs": [], - "watcher": "client" - }, - "highlightRelated": { - "breakPoints": { - "enable": true - }, - "closureCaptures": { - "enable": true - }, - "exitPoints": { - "enable": true - }, - "references": { - "enable": true - }, - "yieldPoints": { - "enable": true - } - }, - "hover": { - "actions": { - "debug": { - "enable": true - }, - "enable": true, - "gotoTypeDef": { - "enable": true - }, - "implementations": { - "enable": true - }, - "references": { - "enable": false - }, - "run": { - "enable": true - } - }, - "documentation": { - "enable": true, - "keywords": { - "enable": true - } - }, - "links": { - "enable": true - }, - "memoryLayout": { - "alignment": "hexadecimal", - "enable": true, - "niches": false, - "offset": "hexadecimal", - "size": "both" - } - }, - "imports": { - "granularity": { - "enforce": false, - "group": "crate" - }, - "group": { - "enable": true - }, - "merge": { - "glob": true - }, - "preferNoStd": false, - "preferPrelude": false, - "prefix": "plain" - }, - "inlayHints": { - "bindingModeHints": { - "enable": false - }, - "chainingHints": { - "enable": true - }, - "closingBraceHints": { - "enable": true, - "minLines": 25 - }, - "closureCaptureHints": { - "enable": false - }, - "closureReturnTypeHints": { - "enable": "never" - }, - "closureStyle": "impl_fn", - "discriminantHints": { - "enable": "never" - }, - "expressionAdjustmentHints": { - "enable": "never", - "hideOutsideUnsafe": false, - "mode": "prefix" - }, - "lifetimeElisionHints": { - "enable": "never", - "useParameterNames": false - }, - "maxLength": 25, - "parameterHints": { - "enable": true - }, - "reborrowHints": { - "enable": "never" - }, - "renderColons": true, - "typeHints": { - "enable": true, - "hideClosureInitialization": false, - "hideNamedConstructor": false - } - }, - "interpret": { - "tests": false - }, - "joinLines": { - "joinAssignments": true, - "joinElseIf": true, - "removeTrailingComma": true, - "unwrapTrivialBlock": true - }, - "lens": { - "debug": { - "enable": true - }, - "enable": true, - "forceCustomCommands": true, - "implementations": { - "enable": true - }, - "location": "above_name", - "references": { - "adt": { - "enable": false - }, - "enumVariant": { - "enable": false - }, - "method": { - "enable": false - }, - "trait": { - "enable": false - } - }, - "run": { - "enable": true - } - }, - "linkedProjects": [], - "lru": { - "capacity": null, - "query": { - "capacities": {} - } - }, - "notifications": { - "cargoTomlNotFound": true - }, - "numThreads": null, - "procMacro": { - "attributes": { - "enable": true - }, - "enable": true, - "ignored": {}, - "server": null - }, - "references": { - "excludeImports": false - }, - "rust": { - "analyzerTargetDir": null - }, - "rustc": { - "source": null - }, - "rustfmt": { - "extraArgs": [], - "overrideCommand": null, - "rangeFormatting": { - "enable": false - } - }, - "semanticHighlighting": { - "doc": { - "comment": { - "inject": { - "enable": true - } - } - }, - "nonStandardTokens": true, - "operator": { - "enable": true, - "specialization": { - "enable": false - } - }, - "punctuation": { - "enable": false, - "separate": { - "macro": { - "bang": false - } - }, - "specialization": { - "enable": false - } - }, - "strings": { - "enable": true - } - }, - "signatureInfo": { - "detail": "full", - "documentation": { - "enable": true - } - }, - "workspace": { - "symbol": { - "search": { - "kind": "only_types", - "limit": 128, - "scope": "workspace" - } - } - } - }, - "trace": "verbose", - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/rust_analyzer/runtime_dependencies.json b/src/multilspy/language_servers/rust_analyzer/runtime_dependencies.json deleted file mode 100644 index 517d8b87..00000000 --- a/src/multilspy/language_servers/rust_analyzer/runtime_dependencies.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "_description": "Used to download the runtime dependencies for running RustAnalyzer. Obtained from https://github.com/rust-lang/rust-analyzer/releases", - "runtimeDependencies": [ - { - "id": "RustAnalyzer", - "description": "RustAnalyzer for Linux (x64)", - "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-aarch64-apple-darwin.gz", - "platformId": "osx-arm64", - "archiveType": "gz", - "binaryName": "rust_analyzer" - }, - { - "id": "RustAnalyzer", - "description": "RustAnalyzer for Linux (x64)", - "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-x86_64-unknown-linux-gnu.gz", - "platformId": "linux-x64", - "archiveType": "gz", - "binaryName": "rust_analyzer" - }, - { - "id": "RustAnalyzer", - "description": "RustAnalyzer for Windows (x64)", - "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-x86_64-pc-windows-msvc.zip", - "platformId": "win-x64", - "archiveType": "zip", - "binaryName": "rust-analyzer.exe" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/rust_analyzer/rust_analyzer.py b/src/multilspy/language_servers/rust_analyzer/rust_analyzer.py deleted file mode 100644 index 65f3598f..00000000 --- a/src/multilspy/language_servers/rust_analyzer/rust_analyzer.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust. -""" - -import asyncio -import json -import logging -import os -import stat -import pathlib -from contextlib import asynccontextmanager -from typing import AsyncIterator - -from overrides import override - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils - - -class RustAnalyzer(LanguageServer): - """ - Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust. - """ - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. - """ - rustanalyzer_executable_path = self.setup_runtime_dependencies(logger, config) - super().__init__( - config, - logger, - repository_root_path, - ProcessLaunchInfo(cmd=rustanalyzer_executable_path, cwd=repository_root_path), - "rust", - ) - self.server_ready = asyncio.Event() - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - return super().is_ignored_dirname(dirname) or dirname in ["target"] - - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: - """ - Setup runtime dependencies for rust_analyzer. - """ - platform_id = PlatformUtils.get_platform_id() - - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: - d = json.load(f) - del d["_description"] - - # assert platform_id.value in [ - # "linux-x64", - # "win-x64", - # ], "Only linux-x64 and win-x64 platform is supported for in multilspy at the moment" - - runtime_dependencies = d["runtimeDependencies"] - runtime_dependencies = [ - dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value - ] - assert len(runtime_dependencies) == 1 - dependency = runtime_dependencies[0] - - rustanalyzer_ls_dir = os.path.join(os.path.dirname(__file__), "static", "RustAnalyzer") - rustanalyzer_executable_path = os.path.join(rustanalyzer_ls_dir, dependency["binaryName"]) - if not os.path.exists(rustanalyzer_ls_dir): - os.makedirs(rustanalyzer_ls_dir) - if dependency["archiveType"] == "gz": - FileUtils.download_and_extract_archive( - logger, dependency["url"], rustanalyzer_executable_path, dependency["archiveType"] - ) - else: - FileUtils.download_and_extract_archive( - logger, dependency["url"], rustanalyzer_ls_dir, dependency["archiveType"] - ) - assert os.path.exists(rustanalyzer_executable_path) - os.chmod(rustanalyzer_executable_path, stat.S_IEXEC) - - return rustanalyzer_executable_path - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the Rust Analyzer Language Server. - """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["RustAnalyzer"]: - """ - Starts the Rust Analyzer Language Server, waits for the server to be ready and yields the LanguageServer instance. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and ready to serve requests - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown - """ - - async def register_capability_handler(params): - assert "registrations" in params - for registration in params["registrations"]: - if registration["method"] == "workspace/executeCommand": - self.initialize_searcher_command_available.set() - self.resolve_main_method_available.set() - return - - async def lang_status_handler(params): - # TODO: Should we wait for - # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} - # Before proceeding? - if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": - self.service_ready_event.set() - - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def check_experimental_status(params): - if params["quiescent"] == True: - self.server_ready.set() - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - self.server.on_request("client/registerCapability", register_capability_handler) - self.server.on_notification("language/status", lang_status_handler) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - self.server.on_notification("experimental/serverStatus", check_experimental_status) - - async with super().start_server(): - self.logger.log("Starting RustAnalyzer server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 - assert "completionProvider" in init_response["capabilities"] - assert init_response["capabilities"]["completionProvider"] == { - "resolveProvider": True, - "triggerCharacters": [":", ".", "'", "("], - "completionItem": {"labelDetailsSupport": True}, - } - self.server.notify.initialized({}) - self.completions_available.set() - - await self.server_ready.wait() - - yield self diff --git a/src/multilspy/language_servers/solargraph/initialize_params.json b/src/multilspy/language_servers/solargraph/initialize_params.json deleted file mode 100644 index 2cbb41f8..00000000 --- a/src/multilspy/language_servers/solargraph/initialize_params.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "_description": "This file contains the initialization parameters for the Solargraph Language Server.", - "processId": "$processId", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": { - }, - "trace": "verbose", - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] -} diff --git a/src/multilspy/language_servers/solargraph/runtime_dependencies.json b/src/multilspy/language_servers/solargraph/runtime_dependencies.json deleted file mode 100644 index 38cc2372..00000000 --- a/src/multilspy/language_servers/solargraph/runtime_dependencies.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "_description": "This file contains URLs and other metadata required for downloading and installing the Solargraph language server.", - "runtimeDependencies": [ - { - "url": "https://rubygems.org/downloads/solargraph-0.51.1.gem", - "installCommand": "gem install solargraph -v 0.51.1", - "binaryName": "solargraph", - "archiveType": "gem" - } - ] -} diff --git a/src/multilspy/language_servers/solargraph/solargraph.py b/src/multilspy/language_servers/solargraph/solargraph.py deleted file mode 100644 index 099ff9d6..00000000 --- a/src/multilspy/language_servers/solargraph/solargraph.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -Provides Ruby specific instantiation of the LanguageServer class using Solargraph. -Contains various configurations and settings specific to Ruby. -""" - -import asyncio -import json -import logging -import os -import stat -import subprocess -import pathlib -from contextlib import asynccontextmanager -from typing import AsyncIterator, override - -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils, PlatformId - - -class Solargraph(LanguageServer): - """ - Provides Ruby specific instantiation of the LanguageServer class using Solargraph. - Contains various configurations and settings specific to Ruby. - """ - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - 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) - super().__init__( - config, - logger, - repository_root_path, - ProcessLaunchInfo(cmd=f"{solargraph_executable_path} stdio", cwd=repository_root_path), - "ruby", - ) - self.server_ready = asyncio.Event() - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - return super().is_ignored_dirname(dirname) or dirname in ["vendor"] - - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig, repository_root_path: str) -> str: - """ - Setup runtime dependencies for Solargraph. - """ - - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: - d = json.load(f) - del d["_description"] - - dependency = d["runtimeDependencies"][0] - - # Check if Ruby is installed - try: - result = subprocess.run(["ruby", "--version"], check=True, capture_output=True, cwd=repository_root_path) - ruby_version = result.stdout.strip() - logger.log(f"Ruby version: {ruby_version}", logging.INFO) - except subprocess.CalledProcessError as e: - raise RuntimeError(f"Error checking for Ruby installation: {e.stderr}") - except FileNotFoundError: - raise RuntimeError("Ruby is not installed. Please install Ruby before continuing.") - - # Check if solargraph is installed - try: - result = subprocess.run(["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) - subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path) - - # Get the gem executable path directly - result = subprocess.run(["gem", "which", "solargraph"], check=True, capture_output=True, text=True, cwd=repository_root_path) - gem_path = result.stdout.strip() - bin_dir = os.path.join(os.path.dirname(os.path.dirname(gem_path)), "bin") - executable_path = os.path.join(bin_dir, "solargraph") - - if not os.path.exists(executable_path): - raise RuntimeError(f"Solargraph executable not found at {executable_path}") - - # Ensure the executable has the right permissions - os.chmod(executable_path, os.stat(executable_path).st_mode | stat.S_IEXEC) - - return executable_path - except subprocess.CalledProcessError as e: - raise RuntimeError(f"Failed to check or install Solargraph. {e.stderr}") - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the Solargraph Language Server. - """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["Solargraph"]: - """ - Starts the Solargraph Language Server for Ruby, waits for the server to be ready and yields the LanguageServer instance. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and ready to serve requests - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown - """ - - async def register_capability_handler(params): - assert "registrations" in params - for registration in params["registrations"]: - if registration["method"] == "workspace/executeCommand": - self.initialize_searcher_command_available.set() - self.resolve_main_method_available.set() - return - - async def lang_status_handler(params): - # TODO: Should we wait for - # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} - # Before proceeding? - if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": - self.service_ready_event.set() - - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - self.server.on_request("client/registerCapability", register_capability_handler) - self.server.on_notification("language/status", lang_status_handler) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("language/actionableNotification", do_nothing) - - async with super().start_server(): - self.logger.log("Starting solargraph server process", logging.INFO) - await 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) - init_response = await self.server.send.initialize(initialize_params) - self.logger.log(f"Received init response: {init_response}", logging.INFO) - assert init_response["capabilities"]["textDocumentSync"] == 2 - assert "completionProvider" in init_response["capabilities"] - assert init_response["capabilities"]["completionProvider"] == { - "resolveProvider": True, - "triggerCharacters": [".", ":", "@"], - } - self.server.notify.initialized({}) - self.completions_available.set() - - self.server_ready.set() - await self.server_ready.wait() - - yield self diff --git a/src/multilspy/language_servers/typescript_language_server/initialize_params.json b/src/multilspy/language_servers/typescript_language_server/initialize_params.json deleted file mode 100644 index 6209ba47..00000000 --- a/src/multilspy/language_servers/typescript_language_server/initialize_params.json +++ /dev/null @@ -1,906 +0,0 @@ -{ - "_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize", - "processId": "os.getpid()", - "clientInfo": { - "name": "Visual Studio Code - Insiders", - "version": "1.82.0-insider" - }, - "locale": "en", - "rootPath": "$rootPath", - "rootUri": "$rootUri", - "capabilities": { - "workspace": { - "applyEdit": true, - "workspaceEdit": { - "documentChanges": true, - "resourceOperations": [ - "create", - "rename", - "delete" - ], - "failureHandling": "textOnlyTransactional", - "normalizesLineEndings": true, - "changeAnnotationSupport": { - "groupsOnLabel": true - } - }, - "configuration": true, - "didChangeWatchedFiles": { - "dynamicRegistration": true, - "relativePatternSupport": true - }, - "symbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "resolveSupport": { - "properties": [ - "location.range" - ] - } - }, - "codeLens": { - "refreshSupport": true - }, - "executeCommand": { - "dynamicRegistration": true - }, - "didChangeConfiguration": { - "dynamicRegistration": true - }, - "workspaceFolders": true, - "semanticTokens": { - "refreshSupport": true - }, - "fileOperations": { - "dynamicRegistration": true, - "didCreate": true, - "didRename": true, - "didDelete": true, - "willCreate": true, - "willRename": true, - "willDelete": true - }, - "inlineValue": { - "refreshSupport": true - }, - "inlayHint": { - "refreshSupport": true - }, - "diagnostics": { - "refreshSupport": true - } - }, - "textDocument": { - "publishDiagnostics": { - "relatedInformation": true, - "versionSupport": false, - "tagSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "codeDescriptionSupport": true, - "dataSupport": true - }, - "synchronization": { - "dynamicRegistration": true, - "willSave": true, - "willSaveWaitUntil": true, - "didSave": true - }, - "completion": { - "dynamicRegistration": true, - "contextSupport": true, - "completionItem": { - "snippetSupport": true, - "commitCharactersSupport": true, - "documentationFormat": [ - "markdown", - "plaintext" - ], - "deprecatedSupport": true, - "preselectSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "insertReplaceSupport": true, - "resolveSupport": { - "properties": [ - "documentation", - "detail", - "additionalTextEdits" - ] - }, - "insertTextModeSupport": { - "valueSet": [ - 1, - 2 - ] - }, - "labelDetailsSupport": true - }, - "insertTextMode": 2, - "completionItemKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25 - ] - }, - "completionList": { - "itemDefaults": [ - "commitCharacters", - "editRange", - "insertTextFormat", - "insertTextMode" - ] - } - }, - "hover": { - "dynamicRegistration": true, - "contentFormat": [ - "markdown", - "plaintext" - ] - }, - "signatureHelp": { - "dynamicRegistration": true, - "signatureInformation": { - "documentationFormat": [ - "markdown", - "plaintext" - ], - "parameterInformation": { - "labelOffsetSupport": true - }, - "activeParameterSupport": true - }, - "contextSupport": true - }, - "definition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "references": { - "dynamicRegistration": true - }, - "documentHighlight": { - "dynamicRegistration": true - }, - "documentSymbol": { - "dynamicRegistration": true, - "symbolKind": { - "valueSet": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26 - ] - }, - "hierarchicalDocumentSymbolSupport": true, - "tagSupport": { - "valueSet": [ - 1 - ] - }, - "labelSupport": true - }, - "codeAction": { - "dynamicRegistration": true, - "isPreferredSupport": true, - "disabledSupport": true, - "dataSupport": true, - "resolveSupport": { - "properties": [ - "edit" - ] - }, - "codeActionLiteralSupport": { - "codeActionKind": { - "valueSet": [ - "", - "quickfix", - "refactor", - "refactor.extract", - "refactor.inline", - "refactor.rewrite", - "source", - "source.organizeImports" - ] - } - }, - "honorsChangeAnnotations": false - }, - "codeLens": { - "dynamicRegistration": true - }, - "formatting": { - "dynamicRegistration": true - }, - "rangeFormatting": { - "dynamicRegistration": true - }, - "onTypeFormatting": { - "dynamicRegistration": true - }, - "rename": { - "dynamicRegistration": true, - "prepareSupport": true, - "prepareSupportDefaultBehavior": 1, - "honorsChangeAnnotations": true - }, - "documentLink": { - "dynamicRegistration": true, - "tooltipSupport": true - }, - "typeDefinition": { - "dynamicRegistration": true, - "linkSupport": true - }, - "implementation": { - "dynamicRegistration": true, - "linkSupport": true - }, - "colorProvider": { - "dynamicRegistration": true - }, - "foldingRange": { - "dynamicRegistration": true, - "rangeLimit": 5000, - "lineFoldingOnly": true, - "foldingRangeKind": { - "valueSet": [ - "comment", - "imports", - "region" - ] - }, - "foldingRange": { - "collapsedText": false - } - }, - "declaration": { - "dynamicRegistration": true, - "linkSupport": true - }, - "selectionRange": { - "dynamicRegistration": true - }, - "callHierarchy": { - "dynamicRegistration": true - }, - "semanticTokens": { - "dynamicRegistration": true, - "tokenTypes": [ - "namespace", - "type", - "class", - "enum", - "interface", - "struct", - "typeParameter", - "parameter", - "variable", - "property", - "enumMember", - "event", - "function", - "method", - "macro", - "keyword", - "modifier", - "comment", - "string", - "number", - "regexp", - "operator", - "decorator" - ], - "tokenModifiers": [ - "declaration", - "definition", - "readonly", - "static", - "deprecated", - "abstract", - "async", - "modification", - "documentation", - "defaultLibrary" - ], - "formats": [ - "relative" - ], - "requests": { - "range": true, - "full": { - "delta": true - } - }, - "multilineTokenSupport": false, - "overlappingTokenSupport": false, - "serverCancelSupport": true, - "augmentsSyntaxTokens": false - }, - "linkedEditingRange": { - "dynamicRegistration": true - }, - "typeHierarchy": { - "dynamicRegistration": true - }, - "inlineValue": { - "dynamicRegistration": true - }, - "inlayHint": { - "dynamicRegistration": true, - "resolveSupport": { - "properties": [ - "tooltip", - "textEdits", - "label.tooltip", - "label.location", - "label.command" - ] - } - }, - "diagnostic": { - "dynamicRegistration": true, - "relatedDocumentSupport": false - } - }, - "general": { - "staleRequestSupport": { - "cancel": true, - "retryOnContentModified": [ - "textDocument/semanticTokens/full", - "textDocument/semanticTokens/range", - "textDocument/semanticTokens/full/delta" - ] - }, - "regularExpressions": { - "engine": "ECMAScript", - "version": "ES2020" - }, - "markdown": { - "parser": "marked", - "version": "1.1.0", - "allowedTags": [ - "ul", - "li", - "p", - "code", - "blockquote", - "ol", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "hr", - "em", - "pre", - "table", - "thead", - "tbody", - "tr", - "th", - "td", - "div", - "del", - "a", - "strong", - "br", - "img", - "span" - ] - }, - "positionEncodings": [ - "utf-16" - ] - }, - "notebookDocument": { - "synchronization": { - "dynamicRegistration": true, - "executionSummarySupport": true - } - }, - "experimental": { - "snippetTextEdit": true, - "codeActionGroup": true, - "hoverActions": true, - "serverStatusNotification": true, - "colorDiagnosticOutput": true, - "openServerLogs": true, - "localDocs": true, - "commands": { - "commands": [ - "rust-analyzer.runSingle", - "rust-analyzer.debugSingle", - "rust-analyzer.showReferences", - "rust-analyzer.gotoLocation", - "editor.action.triggerParameterHints" - ] - } - } - }, - "initializationOptions": { - "cargoRunner": null, - "runnables": { - "extraEnv": null, - "problemMatcher": [ - "$rustc" - ], - "command": null, - "extraArgs": [] - }, - "statusBar": { - "clickAction": "openLogs" - }, - "server": { - "path": null, - "extraEnv": null - }, - "trace": { - "server": "verbose", - "extension": false - }, - "debug": { - "engine": "auto", - "sourceFileMap": { - "/rustc/": "${env:USERPROFILE}/.rustup/toolchains//lib/rustlib/src/rust" - }, - "openDebugPane": false, - "engineSettings": {} - }, - "restartServerOnConfigChange": false, - "typing": { - "continueCommentsOnNewline": true, - "autoClosingAngleBrackets": { - "enable": false - } - }, - "diagnostics": { - "previewRustcOutput": false, - "useRustcErrorCode": false, - "disabled": [], - "enable": true, - "experimental": { - "enable": false - }, - "remapPrefix": {}, - "warningsAsHint": [], - "warningsAsInfo": [] - }, - "discoverProjectRunner": null, - "showUnlinkedFileNotification": true, - "showDependenciesExplorer": true, - "assist": { - "emitMustUse": false, - "expressionFillDefault": "todo" - }, - "cachePriming": { - "enable": true, - "numThreads": 0 - }, - "cargo": { - "autoreload": true, - "buildScripts": { - "enable": true, - "invocationLocation": "workspace", - "invocationStrategy": "per_workspace", - "overrideCommand": null, - "useRustcWrapper": true - }, - "cfgs": {}, - "extraArgs": [], - "extraEnv": {}, - "features": [], - "noDefaultFeatures": false, - "sysroot": "discover", - "sysrootSrc": null, - "target": null, - "unsetTest": [ - "core" - ] - }, - "checkOnSave": true, - "check": { - "allTargets": true, - "command": "check", - "extraArgs": [], - "extraEnv": {}, - "features": null, - "ignore": [], - "invocationLocation": "workspace", - "invocationStrategy": "per_workspace", - "noDefaultFeatures": null, - "overrideCommand": null, - "targets": null - }, - "completion": { - "autoimport": { - "enable": true - }, - "autoself": { - "enable": true - }, - "callable": { - "snippets": "fill_arguments" - }, - "fullFunctionSignatures": { - "enable": false - }, - "limit": null, - "postfix": { - "enable": true - }, - "privateEditable": { - "enable": false - }, - "snippets": { - "custom": { - "Arc::new": { - "postfix": "arc", - "body": "Arc::new(${receiver})", - "requires": "std::sync::Arc", - "description": "Put the expression into an `Arc`", - "scope": "expr" - }, - "Rc::new": { - "postfix": "rc", - "body": "Rc::new(${receiver})", - "requires": "std::rc::Rc", - "description": "Put the expression into an `Rc`", - "scope": "expr" - }, - "Box::pin": { - "postfix": "pinbox", - "body": "Box::pin(${receiver})", - "requires": "std::boxed::Box", - "description": "Put the expression into a pinned `Box`", - "scope": "expr" - }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, - "Err": { - "postfix": "err", - "body": "Err(${receiver})", - "description": "Wrap the expression in a `Result::Err`", - "scope": "expr" - }, - "Some": { - "postfix": "some", - "body": "Some(${receiver})", - "description": "Wrap the expression in an `Option::Some`", - "scope": "expr" - } - } - } - }, - "files": { - "excludeDirs": [], - "watcher": "client" - }, - "highlightRelated": { - "breakPoints": { - "enable": true - }, - "closureCaptures": { - "enable": true - }, - "exitPoints": { - "enable": true - }, - "references": { - "enable": true - }, - "yieldPoints": { - "enable": true - } - }, - "hover": { - "actions": { - "debug": { - "enable": true - }, - "enable": true, - "gotoTypeDef": { - "enable": true - }, - "implementations": { - "enable": true - }, - "references": { - "enable": false - }, - "run": { - "enable": true - } - }, - "documentation": { - "enable": true, - "keywords": { - "enable": true - } - }, - "links": { - "enable": true - }, - "memoryLayout": { - "alignment": "hexadecimal", - "enable": true, - "niches": false, - "offset": "hexadecimal", - "size": "both" - } - }, - "imports": { - "granularity": { - "enforce": false, - "group": "crate" - }, - "group": { - "enable": true - }, - "merge": { - "glob": true - }, - "preferNoStd": false, - "preferPrelude": false, - "prefix": "plain" - }, - "inlayHints": { - "bindingModeHints": { - "enable": false - }, - "chainingHints": { - "enable": true - }, - "closingBraceHints": { - "enable": true, - "minLines": 25 - }, - "closureCaptureHints": { - "enable": false - }, - "closureReturnTypeHints": { - "enable": "never" - }, - "closureStyle": "impl_fn", - "discriminantHints": { - "enable": "never" - }, - "expressionAdjustmentHints": { - "enable": "never", - "hideOutsideUnsafe": false, - "mode": "prefix" - }, - "lifetimeElisionHints": { - "enable": "never", - "useParameterNames": false - }, - "maxLength": 25, - "parameterHints": { - "enable": true - }, - "reborrowHints": { - "enable": "never" - }, - "renderColons": true, - "typeHints": { - "enable": true, - "hideClosureInitialization": false, - "hideNamedConstructor": false - } - }, - "interpret": { - "tests": false - }, - "joinLines": { - "joinAssignments": true, - "joinElseIf": true, - "removeTrailingComma": true, - "unwrapTrivialBlock": true - }, - "lens": { - "debug": { - "enable": true - }, - "enable": true, - "forceCustomCommands": true, - "implementations": { - "enable": true - }, - "location": "above_name", - "references": { - "adt": { - "enable": false - }, - "enumVariant": { - "enable": false - }, - "method": { - "enable": false - }, - "trait": { - "enable": false - } - }, - "run": { - "enable": true - } - }, - "linkedProjects": [], - "lru": { - "capacity": null, - "query": { - "capacities": {} - } - }, - "notifications": { - "cargoTomlNotFound": true - }, - "numThreads": null, - "procMacro": { - "attributes": { - "enable": true - }, - "enable": true, - "ignored": {}, - "server": null - }, - "references": { - "excludeImports": false - }, - "rust": { - "analyzerTargetDir": null - }, - "rustc": { - "source": null - }, - "rustfmt": { - "extraArgs": [], - "overrideCommand": null, - "rangeFormatting": { - "enable": false - } - }, - "semanticHighlighting": { - "doc": { - "comment": { - "inject": { - "enable": true - } - } - }, - "nonStandardTokens": true, - "operator": { - "enable": true, - "specialization": { - "enable": false - } - }, - "punctuation": { - "enable": false, - "separate": { - "macro": { - "bang": false - } - }, - "specialization": { - "enable": false - } - }, - "strings": { - "enable": true - } - }, - "signatureInfo": { - "detail": "full", - "documentation": { - "enable": true - } - }, - "workspace": { - "symbol": { - "search": { - "kind": "only_types", - "limit": 128, - "scope": "workspace" - } - } - } - }, - "trace": "verbose", - "workspaceFolders": [ - { - "uri": "$uri", - "name": "$name" - } - ] -} \ No newline at end of file diff --git a/src/multilspy/language_servers/typescript_language_server/runtime_dependencies.json b/src/multilspy/language_servers/typescript_language_server/runtime_dependencies.json deleted file mode 100644 index 90824a7c..00000000 --- a/src/multilspy/language_servers/typescript_language_server/runtime_dependencies.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "_description": "Used to download the runtime dependencies for running typescript-language-server. Obtained from https://github.com/typescript-language-server/typescript-language-server/releases", - "runtimeDependencies": [ - { - "id": "typescript", - "description": "typescript package for Linux, OSX, and Windows. Both x64 and arm64 are supported.", - "command": "npm install --prefix ./ typescript@5.5.4" - }, - { - "id": "typescript-language-server", - "description": "typescript-language-server package for Linux, OSX, and Windows. Both x64 and arm64 are supported.", - "command": "npm install --prefix ./ typescript-language-server@4.3.3" - } - ] -} - diff --git a/src/multilspy/language_servers/typescript_language_server/typescript_language_server.py b/src/multilspy/language_servers/typescript_language_server/typescript_language_server.py deleted file mode 100644 index 479936c9..00000000 --- a/src/multilspy/language_servers/typescript_language_server/typescript_language_server.py +++ /dev/null @@ -1,245 +0,0 @@ -""" -Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript. -""" - -import asyncio -import json -import logging -import os -import pathlib -import shutil -import subprocess -from collections.abc import AsyncIterator -from contextlib import asynccontextmanager -from time import sleep - -from overrides import override - -from multilspy.language_server import LanguageServer -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import PlatformId, PlatformUtils - -# Platform-specific imports -if os.name != 'nt': # Unix-like systems - import pwd -else: - # Dummy pwd module for Windows - class pwd: - @staticmethod - def getpwuid(uid): - return type('obj', (), {'pw_name': os.environ.get('USERNAME', 'unknown')})() - - -# Conditionally import pwd module (Unix-only) -if not PlatformUtils.get_platform_id().value.startswith("win"): - import pwd - - -class TypeScriptLanguageServer(LanguageServer): - """ - Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript. - """ - - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): - """ - 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) - super().__init__( - config, - logger, - repository_root_path, - ProcessLaunchInfo(cmd=ts_lsp_executable_path, cwd=repository_root_path), - "typescript", - ) - self.server_ready = asyncio.Event() - - @override - def is_ignored_dirname(self, dirname: str) -> bool: - return super().is_ignored_dirname(dirname) or dirname in [ - "node_modules", - "dist", - "build", - "coverage", - ] - - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: - """ - Setup runtime dependencies for TypeScript Language Server. - """ - platform_id = PlatformUtils.get_platform_id() - - valid_platforms = [ - PlatformId.LINUX_x64, - PlatformId.LINUX_arm64, - PlatformId.OSX, - PlatformId.OSX_x64, - PlatformId.OSX_arm64, - PlatformId.WIN_x64, - PlatformId.WIN_arm64, - ] - assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment" - - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json")) as f: - d = json.load(f) - del d["_description"] - - runtime_dependencies = d.get("runtimeDependencies", []) - tsserver_ls_dir = os.path.join(os.path.dirname(__file__), "static", "ts-lsp") - tsserver_executable_path = os.path.join(tsserver_ls_dir, "typescript-language-server") - - # Verify both node and npm are installed - is_node_installed = shutil.which('node') is not None - assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again." - is_npm_installed = shutil.which('npm') is not None - assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again." - - # Install typescript and typescript-language-server if not already installed - if not os.path.exists(tsserver_ls_dir): - os.makedirs(tsserver_ls_dir, exist_ok=True) - for dependency in runtime_dependencies: - # Windows doesn't support the 'user' parameter and doesn't have pwd module - if PlatformUtils.get_platform_id().value.startswith("win"): - subprocess.run( - dependency["command"], - shell=True, - check=True, - cwd=tsserver_ls_dir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL - ) - else: - # On Unix-like systems, run as non-root user - user = pwd.getpwuid(os.getuid()).pw_name - subprocess.run( - dependency["command"], - shell=True, - check=True, - user=user, - cwd=tsserver_ls_dir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL - ) - - tsserver_executable_path = os.path.join(tsserver_ls_dir, "node_modules", ".bin", "typescript-language-server") - - assert os.path.exists(tsserver_executable_path), "typescript-language-server executable not found. Please install typescript-language-server and try again." - return f"{tsserver_executable_path} --stdio" - - def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the TypeScript Language Server. - """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json")) as f: - d = json.load(f) - - del d["_description"] - - d["processId"] = os.getpid() - assert d["rootPath"] == "$rootPath" - d["rootPath"] = repository_absolute_path - - assert d["rootUri"] == "$rootUri" - d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() - - assert d["workspaceFolders"][0]["name"] == "$name" - d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) - - return d - - @asynccontextmanager - async def start_server(self) -> AsyncIterator["TypeScriptLanguageServer"]: - """ - Starts the TypeScript Language Server, waits for the server to be ready and yields the LanguageServer instance. - - Usage: - ``` - async with lsp.start_server(): - # LanguageServer has been initialized and ready to serve requests - await lsp.request_definition(...) - await lsp.request_references(...) - # Shutdown the LanguageServer on exit from scope - # LanguageServer has been shutdown - """ - - async def register_capability_handler(params): - assert "registrations" in params - for registration in params["registrations"]: - if registration["method"] == "workspace/executeCommand": - self.initialize_searcher_command_available.set() - # TypeScript doesn't have a direct equivalent to resolve_main_method - # You might want to set a different flag or remove this line - # self.resolve_main_method_available.set() - return - - async def execute_client_command_handler(params): - return [] - - async def do_nothing(params): - return - - async def window_log_message(msg): - self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - - async def check_experimental_status(params): - """ - Also listen for experimental/serverStatus as a backup signal - """ - if params.get("quiescent") == True: - self.server_ready.set() - self.completions_available.set() - - self.server.on_request("client/registerCapability", register_capability_handler) - self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) - self.server.on_notification("$/progress", do_nothing) - self.server.on_notification("textDocument/publishDiagnostics", do_nothing) - self.server.on_notification("experimental/serverStatus", check_experimental_status) - - async with super().start_server(): - self.logger.log("Starting TypeScript server process", logging.INFO) - await 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, - ) - init_response = await self.server.send.initialize(initialize_params) - - # TypeScript-specific capability checks - assert init_response["capabilities"]["textDocumentSync"] == 2 - assert "completionProvider" in init_response["capabilities"] - assert init_response["capabilities"]["completionProvider"] == { - "triggerCharacters": ['.', '"', "'", '/', '@', '<'], - "resolveProvider": True - } - - self.server.notify.initialized({}) - try: - await asyncio.wait_for(self.server_ready.wait(), timeout=1.0) - except asyncio.TimeoutError: - self.logger.log("Timeout waiting for TypeScript server to become ready, proceeding anyway", logging.INFO) - # Fallback: assume server is ready after timeout - self.server_ready.set() - self.completions_available.set() - - yield self - - @override - # For some reason, the LS may need longer to process this, so we just retry - async def _send_references_request(self, relative_file_path: str, line: int, column: int): - # TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is - # despite the LS having processed requests already. I don't know what causes this, but sleeping - # one second helps. It may be that sleeping only once is enough but that's hard to reliably test. - # It may be that even this 1sec is not enough in larger TS projects, at some point we should find what - # causes this and solve it. - sleep(1) - return await super()._send_references_request(relative_file_path, line, column) diff --git a/src/multilspy/lsp_protocol_handler/server.py b/src/multilspy/lsp_protocol_handler/server.py deleted file mode 100644 index a2241433..00000000 --- a/src/multilspy/lsp_protocol_handler/server.py +++ /dev/null @@ -1,657 +0,0 @@ -""" -This file provides the implementation of the JSON-RPC client, that launches and -communicates with the language server. - -The initial implementation of this file was obtained from -https://github.com/predragnikolic/OLSP under the MIT License with the following terms: - -MIT License - -Copyright (c) 2023 Предраг Николић - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -import asyncio -import dataclasses -import json -import logging -import os -import threading - -import psutil -from typing import Any, Callable, Dict, List, Optional, Union - -from .lsp_requests import LspNotification, LspRequest -from .lsp_types import ErrorCodes -from ..multilspy_exceptions import MultilspyException - -StringDict = Dict[str, Any] -PayloadLike = Union[List[StringDict], StringDict, None] -CONTENT_LENGTH = "Content-Length: " -ENCODING = "utf-8" -log = logging.getLogger(__name__) - - -@dataclasses.dataclass -class ProcessLaunchInfo: - """ - This class is used to store the information required to launch a process. - """ - - # The command to launch the process - cmd: str - - # The environment variables to set for the process - env: Dict[str, str] = dataclasses.field(default_factory=dict) - - # The working directory for the process - cwd: str = os.getcwd() - - -class Error(Exception): - def __init__(self, code: ErrorCodes, message: str) -> None: - super().__init__(message) - self.code = code - - def to_lsp(self) -> StringDict: - return {"code": self.code, "message": super().__str__()} - - @classmethod - def from_lsp(cls, d: StringDict) -> "Error": - return Error(d["code"], d["message"]) - - def __str__(self) -> str: - return f"{super().__str__()} ({self.code})" - - -def make_response(request_id: Any, params: PayloadLike) -> StringDict: - return {"jsonrpc": "2.0", "id": request_id, "result": params} - - -def make_error_response(request_id: Any, err: Error) -> StringDict: - return {"jsonrpc": "2.0", "id": request_id, "error": err.to_lsp()} - - -def make_notification(method: str, params: PayloadLike) -> StringDict: - return {"jsonrpc": "2.0", "method": method, "params": params} - - -def make_request(method: str, request_id: Any, params: PayloadLike) -> StringDict: - return {"jsonrpc": "2.0", "method": method, "id": request_id, "params": params} - - -class StopLoopException(Exception): - pass - - -def create_message(payload: PayloadLike): - body = json.dumps(payload, check_circular=False, ensure_ascii=False, separators=(",", ":")).encode(ENCODING) - return ( - f"Content-Length: {len(body)}\r\n".encode(ENCODING), - "Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n".encode(ENCODING), - body, - ) - - -class MessageType: - error = 1 - warning = 2 - info = 3 - log = 4 - - -class Request: - def __init__(self) -> None: - self.cv = asyncio.Condition() - self.result: Optional[PayloadLike] = None - self.error: Optional[Error] = None - - async def on_result(self, params: PayloadLike) -> None: - self.result = params - async with self.cv: - self.cv.notify() - - async def on_error(self, err: Error) -> None: - self.error = err - async with self.cv: - self.cv.notify() - - -def content_length(line: bytes) -> Optional[int]: - if line.startswith(b"Content-Length: "): - _, value = line.split(b"Content-Length: ") - value = value.strip() - try: - return int(value) - except ValueError: - raise ValueError("Invalid Content-Length header: {}".format(value)) - return None - - -class LanguageServerHandler: - """ - This class provides the implementation of Python client for the Language Server Protocol. - A class that launches the language server and communicates with it - using the Language Server Protocol (LSP). - - It provides methods for sending requests, responses, and notifications to the server - and for registering handlers for requests and notifications from the server. - - Uses JSON-RPC 2.0 for communication with the server over stdin/stdout. - - Attributes: - send: A LspRequest object that can be used to send requests to the server and - await for the responses. - notify: A LspNotification object that can be used to send notifications to the server. - cmd: A string that represents the command to launch the language server process. - process: A subprocess.Popen object that represents the language server process. - _received_shutdown: A boolean flag that indicates whether the client has received - a shutdown request from the server. - request_id: An integer that represents the next available request id for the client. - _response_handlers: A dictionary that maps request ids to Request objects that - store the results or errors of the requests. - on_request_handlers: A dictionary that maps method names to callback functions - that handle requests from the server. - on_notification_handlers: A dictionary that maps method names to callback functions - that handle notifications from the server. - logger: An optional function that takes two strings (source and destination) and - a payload dictionary, and logs the communication between the client and the server. - tasks: A dictionary that maps task ids to asyncio.Task objects that represent - the asynchronous tasks created by the handler. - task_counter: An integer that represents the next available task id for the handler. - loop: An asyncio.AbstractEventLoop object that represents the event loop used by the handler. - start_independent_lsp_process: An optional boolean flag that indicates whether to start the - language server process in an independent process group. Default is `True`. Setting it to - `False` means that the language server process will be in the same process group as the - the current process, and any SIGINT and SIGTERM signals will be sent to both processes. - """ - - def __init__( - self, - process_launch_info: ProcessLaunchInfo, - logger: Optional[Callable[[str, str, StringDict | str], None]] = None, - start_independent_lsp_process=True, - ) -> None: - """ - Params: - cmd: A string that represents the command to launch the language server process. - logger: An optional function that takes two strings (source and destination) and - a payload dictionary, and logs the communication between the client and the server. - """ - self.send = LspRequest(self.send_request) - self.notify = LspNotification(self.send_notification) - - self.process_launch_info = process_launch_info - self.process = None - self._received_shutdown = False - - self.request_id = 1 - self._response_handlers: Dict[Any, Request] = {} - self.on_request_handlers = {} - self.on_notification_handlers = {} - self.logger = logger - self.tasks = {} - self.task_counter = 0 - self.loop = None - self.start_independent_lsp_process = start_independent_lsp_process - - # Add thread locks for shared resources to prevent race conditions - self._stdin_lock = threading.Lock() - self._request_id_lock = threading.Lock() - self._response_handlers_lock = threading.Lock() - self._tasks_lock = threading.Lock() - - - - def is_running(self) -> bool: - """ - Checks if the language server process is currently running. - """ - return self.process is not None and self.process.returncode is None - - async def start(self) -> None: - """ - Starts the language server process and creates a task to continuously read from its stdout to handle communications - from the server to the client - """ - child_proc_env = os.environ.copy() - child_proc_env.update(self.process_launch_info.env) - - log.info("Starting language server process via command: %s", self.process_launch_info.cmd) - self.process = await asyncio.create_subprocess_shell( - self.process_launch_info.cmd, - stdout=asyncio.subprocess.PIPE, - stdin=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - env=child_proc_env, - cwd=self.process_launch_info.cwd, - start_new_session=self.start_independent_lsp_process, - ) - - # Check if process terminated immediately - if self.process.returncode is not None: - log.error("Language server has already terminated/could not be started") - # Process has already terminated - stderr_data = await self.process.stderr.read() - error_message = stderr_data.decode('utf-8', errors='replace') - raise RuntimeError(f"Process terminated immediately with code {self.process.returncode}. Error: {error_message}") - - self.loop = asyncio.get_event_loop() - - # Use lock to prevent race conditions on tasks and task_counter during startup - with self._tasks_lock: - self.tasks[self.task_counter] = self.loop.create_task(self.run_forever()) - self.task_counter += 1 - self.tasks[self.task_counter] = self.loop.create_task(self.run_forever_stderr()) - self.task_counter += 1 - - - - async def stop(self) -> None: - """ - Sends the terminate signal to the language server process and waits for it to exit, with a timeout, killing it if necessary - """ - # First cancel all tasks - await self._cancel_pending_tasks() - - process = self.process - self.process = None - - if not process: - return - - # Clean up the process - await self._cleanup_process(process) - - async def _cancel_pending_tasks(self): - """Cancel all pending tasks and wait for them to complete or timeout.""" - pending_tasks = [] - - # Use lock to safely access tasks dictionary - with self._tasks_lock: - for task in self.tasks.values(): - if not task.done(): - task.cancel() - pending_tasks.append(task) - - if pending_tasks: - try: - await asyncio.wait_for(asyncio.gather(*pending_tasks, return_exceptions=True), timeout=5.0) - except (asyncio.TimeoutError, Exception): - pass - - # Clear tasks dictionary under lock - with self._tasks_lock: - self.tasks = {} - - async def _cleanup_process(self, process): - """Clean up a process: close stdin, terminate/kill process, close stdout/stderr.""" - # Close stdin first to prevent deadlocks - # See: https://bugs.python.org/issue35539 - self._safely_close_pipe(process.stdin) - - # Terminate/kill the process if it's still running - if process.returncode is None: - await self._terminate_or_kill_process(process) - - # Close stdout and stderr pipes after process has exited - # This is essential to prevent "I/O operation on closed pipe" errors and - # "Event loop is closed" errors during garbage collection - # See: https://bugs.python.org/issue41320 and https://github.com/python/cpython/issues/88050 - self._safely_close_pipe(process.stdout) - self._safely_close_pipe(process.stderr) - - # Small delay to ensure OS has released file handles - await asyncio.sleep(0.5) - - def _safely_close_pipe(self, pipe): - """Safely close a pipe, ignoring any exceptions.""" - if pipe: - try: - pipe.close() - except Exception: - pass - - async def _terminate_or_kill_process(self, process): - """Try to terminate the process gracefully, then forcefully if necessary.""" - # First try to terminate the process tree gracefully - self._signal_process_tree(process, terminate=True) - - # Wait for the process to exit (with timeout) - try: - await asyncio.wait_for(process.wait(), timeout=10) - except (asyncio.TimeoutError, Exception): - # If termination failed, forcefully kill the process tree - self._signal_process_tree(process, terminate=False) - try: - # Give it one more chance to exit - await asyncio.wait_for(process.wait(), timeout=2) - except Exception: - pass - - def _signal_process_tree(self, process, terminate=True): - """Send signal (terminate or kill) to the process and all its children.""" - signal_method = "terminate" if terminate else "kill" - - # Try to get the parent process - parent = None - try: - parent = psutil.Process(process.pid) - except (psutil.NoSuchProcess, psutil.AccessDenied, Exception): - pass - - # If we have the parent process and it's running, signal the entire tree - if parent and parent.is_running(): - # Signal children first - for child in parent.children(recursive=True): - try: - getattr(child, signal_method)() - except (psutil.NoSuchProcess, psutil.AccessDenied, Exception): - pass - - # Then signal the parent - try: - getattr(parent, signal_method)() - except (psutil.NoSuchProcess, psutil.AccessDenied, Exception): - pass - else: - # Fall back to direct process signaling - try: - getattr(process, signal_method)() - except Exception: - pass - - - async def shutdown(self) -> None: - """ - Perform the shutdown sequence for the client, including sending the shutdown request to the server and notifying it of exit - """ - self._log("Sending shutdown request to server") - await self.send.shutdown() - self._log("Received shutdown response from server") - self._received_shutdown = True - self._log("Sending exit notification to server") - self.notify.exit() - self._log("Sent exit notification to server") - if self.process and self.process.stdout: - self.process.stdout.set_exception(StopLoopException()) - # This yields the control to the event loop to allow the exception to be handled - # in the run_forever and run_forever_stderr methods - await asyncio.sleep(0) - - def _log(self, message: str | StringDict) -> None: - """ - Create a log message - """ - if self.logger is not None: - self.logger("client", "logger", message) - - async def run_forever(self) -> bool: - """ - Continuously read from the language server process stdout and handle the messages - invoking the registered response and notification handlers - """ - try: - while self.process and self.process.stdout and not self.process.stdout.at_eof(): - line = await self.process.stdout.readline() - if not line: - continue - try: - num_bytes = content_length(line) - except ValueError: - continue - if num_bytes is None: - continue - while line and line.strip(): - line = await self.process.stdout.readline() - if not line: - continue - body = await self.process.stdout.readexactly(num_bytes) - - # Use lock to prevent race conditions on tasks and task_counter - with self._tasks_lock: - self.tasks[self.task_counter] = asyncio.get_event_loop().create_task(self._handle_body(body)) - self.task_counter += 1 - except (BrokenPipeError, ConnectionResetError, StopLoopException): - pass - return self._received_shutdown - - - async def run_forever_stderr(self) -> None: - """ - Continuously read from the language server process stderr and log the messages - """ - try: - while self.process and self.process.stderr and not self.process.stderr.at_eof(): - line = await self.process.stderr.readline() - if not line: - continue - self._log("LSP stderr: " + line.decode(ENCODING, errors='replace')) - except (BrokenPipeError, ConnectionResetError, StopLoopException): - pass - - async def _handle_body(self, body: bytes) -> None: - """ - Parse the body text received from the language server process and invoke the appropriate handler - """ - try: - await self._receive_payload(json.loads(body)) - except IOError as ex: - self._log(f"malformed {ENCODING}: {ex}") - except UnicodeDecodeError as ex: - self._log(f"malformed {ENCODING}: {ex}") - except json.JSONDecodeError as ex: - self._log(f"malformed JSON: {ex}") - - async def _receive_payload(self, payload: StringDict) -> None: - """ - Determine if the payload received from server is for a request, response, or notification and invoke the appropriate handler - """ - if self.logger: - self.logger("server", "client", payload) - try: - if "method" in payload: - if "id" in payload: - await self._request_handler(payload) - else: - await self._notification_handler(payload) - elif "id" in payload: - await self._response_handler(payload) - else: - self._log(f"Unknown payload type: {payload}") - except Exception as err: - self._log(f"Error handling server payload: {err}") - - def send_notification(self, method: str, params: Optional[dict] = None) -> None: - """ - Send notification pertaining to the given method to the server with the given parameters - """ - self._send_payload_sync(make_notification(method, params)) - - def send_response(self, request_id: Any, params: PayloadLike) -> None: - """ - Send response to the given request id to the server with the given parameters - """ - # Use lock to prevent race conditions on tasks and task_counter - with self._tasks_lock: - self.tasks[self.task_counter] = asyncio.get_event_loop().create_task( - self._send_payload(make_response(request_id, params)) - ) - self.task_counter += 1 - - - def send_error_response(self, request_id: Any, err: Error) -> None: - """ - Send error response to the given request id to the server with the given error - """ - # Use lock to prevent race conditions on tasks and task_counter - with self._tasks_lock: - self.tasks[self.task_counter] = asyncio.get_event_loop().create_task( - self._send_payload(make_error_response(request_id, err)) - ) - self.task_counter += 1 - - - async def send_request(self, method: str, params: Optional[dict] = None) -> PayloadLike: - """ - Send request to the server, register the request id, and wait for the response - """ - request = Request() - - # Use lock to prevent race conditions on request_id and _response_handlers - with self._request_id_lock: - request_id = self.request_id - self.request_id += 1 - - with self._response_handlers_lock: - self._response_handlers[request_id] = request - - async with request.cv: - await self._send_payload(make_request(method, request_id, params)) - self._log(f"Waiting for asyncio condition for request {method} with params:\n{params}") - await request.cv.wait() - self._log(f"Finished waiting, processing result") - if isinstance(request.error, Error): - raise MultilspyException(f"Could not process request {method} with params:\n{params}.\n Language server error: {request.error}") from request.error - self._log(f"Returning non-error result, which is:\n{request.result}") - return request.result - - - def _send_payload_sync(self, payload: StringDict) -> None: - """ - Send the payload to the server by writing to its stdin synchronously - """ - if not self.process or not self.process.stdin: - return - msg = create_message(payload) - if self.logger: - self.logger("client", "server", payload) - - # Use lock to prevent concurrent writes to stdin that cause buffer corruption - with self._stdin_lock: - try: - self.process.stdin.writelines(msg) - except (BrokenPipeError, ConnectionResetError, OSError) as e: - # Log the error but don't raise to prevent cascading failures - if self.logger: - self.logger("client", "logger", f"Failed to write to stdin: {e}") - return - - - async def _send_payload(self, payload: StringDict) -> None: - """ - Send the payload to the server by writing to its stdin asynchronously. - """ - if not self.process or not self.process.stdin: - return - self._log(payload) - msg = create_message(payload) - - # Use lock to prevent concurrent writes to stdin that cause buffer corruption - with self._stdin_lock: - try: - self.process.stdin.writelines(msg) - await self.process.stdin.drain() - except (BrokenPipeError, ConnectionResetError, OSError) as e: - # Log the error but don't raise to prevent cascading failures - if self.logger: - self.logger("client", "logger", f"Failed to write to stdin: {e}") - return - - - def on_request(self, method: str, cb) -> None: - """ - Register the callback function to handle requests from the server to the client for the given method - """ - self.on_request_handlers[method] = cb - - def on_notification(self, method: str, cb) -> None: - """ - Register the callback function to handle notifications from the server to the client for the given method - """ - self.on_notification_handlers[method] = cb - - async def _response_handler(self, response: StringDict) -> None: - """ - Handle the response received from the server for a request, using the id to determine the request - """ - with self._response_handlers_lock: - request = self._response_handlers.pop(response["id"]) - - if "result" in response and "error" not in response: - await request.on_result(response["result"]) - elif "result" not in response and "error" in response: - await request.on_error(Error.from_lsp(response["error"])) - else: - await request.on_error(Error(ErrorCodes.InvalidRequest, "")) - - - async def _request_handler(self, response: StringDict) -> None: - """ - Handle the request received from the server: call the appropriate callback function and return the result - """ - method = response.get("method", "") - params = response.get("params") - request_id = response.get("id") - handler = self.on_request_handlers.get(method) - if not handler: - self.send_error_response( - request_id, - Error( - ErrorCodes.MethodNotFound, - "method '{}' not handled on client.".format(method), - ), - ) - return - try: - self.send_response(request_id, await handler(params)) - except Error as ex: - self.send_error_response(request_id, ex) - except Exception as ex: - self.send_error_response(request_id, Error(ErrorCodes.InternalError, str(ex))) - - async def _notification_handler(self, response: StringDict) -> None: - """ - Handle the notification received from the server: call the appropriate callback function - """ - method = response.get("method", "") - params = response.get("params") - handler = self.on_notification_handlers.get(method) - if not handler: - self._log(f"unhandled {method}") - return - try: - await handler(params) - except asyncio.CancelledError: - return - except Exception as ex: - if (not self._received_shutdown) and self.logger: - self.logger( - "client", - "logger", - str( - { - "type": MessageType.error, - "message": str(ex), - "method": method, - "params": params, - } - ), - ) diff --git a/src/multilspy/type_helpers.py b/src/multilspy/type_helpers.py deleted file mode 100644 index 0a805e43..00000000 --- a/src/multilspy/type_helpers.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -This module provides type-helpers used across multilspy implementation -""" - -import inspect - -from typing import Callable, TypeVar, Type - -R = TypeVar("R", bound=object) - -def ensure_all_methods_implemented( - source_cls: Type[object], -) -> Callable[[Type[R]], Type[R]]: - """ - A decorator to ensure that all methods of source_cls class are implemented in the decorated class. - """ - - def check_all_methods_implemented(target_cls: R) -> R: - for name, _ in inspect.getmembers(source_cls, inspect.isfunction): - if name.startswith("_"): - continue - if name not in target_cls.__dict__ or not callable(target_cls.__dict__[name]): - raise NotImplementedError(f"{name} is not implemented in {target_cls}") - - return target_cls - - return check_all_methods_implemented \ No newline at end of file diff --git a/src/serena/agent.py b/src/serena/agent.py index b8cde47c..f2c09416 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -33,10 +33,6 @@ from sensai.util import logging from sensai.util.logging import LOG_DEFAULT_FORMAT, FallbackHandler from sensai.util.string import ToStringMixin, dict_string -from multilspy import SyncLanguageServer -from multilspy.multilspy_config import Language, MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_types import SymbolKind from serena import serena_version from serena.config import SerenaAgentContext, SerenaAgentMode from serena.constants import ( @@ -45,7 +41,6 @@ from serena.constants import ( REPO_ROOT, SELENA_CONFIG_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME, - USE_SOLID_LSP, ) from serena.dashboard import MemoryLogHandler, SerenaDashboardAPI from serena.prompt_factory import PromptFactory, SerenaPromptFactory @@ -56,7 +51,10 @@ from serena.util.general import load_yaml, save_yaml from serena.util.inspection import determine_programming_language_composition, iter_subclasses from serena.util.shell import execute_shell_command from serena.util.thread import ExecutionResult, execute_with_timeout -from solidlsp.ls import SolidLanguageServer +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language, LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_types import SymbolKind if TYPE_CHECKING: from serena.gui_log_viewer import GuiLogViewerHandler @@ -641,7 +639,7 @@ def create_ls_for_project( log_level: int = logging.INFO, ls_timeout: float | None = DEFAULT_TOOL_TIMEOUT - 5, trace_lsp_communication: bool = False, -) -> SyncLanguageServer: +) -> SolidLanguageServer: """ Create a language server for a project. Note that you will have to start it before performing any LS operations. @@ -671,28 +669,19 @@ def create_ls_for_project( log.debug(f"Adding {len(spec.patterns)} patterns from {spec.file_path} to the ignored paths.") ignored_paths.extend(spec.patterns) log.debug(f"Using {len(ignored_paths)} ignored paths in total.") - multilspy_config = MultilspyConfig( + multilspy_config = LanguageServerConfig( code_language=project_instance.language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication, ) - ls_logger = MultilspyLogger(log_level=log_level) + ls_logger = LanguageServerLogger(log_level=log_level) log.info(f"Creating language server instance for {project_instance.project_root}.") - if USE_SOLID_LSP: - ls = SolidLanguageServer.create( - multilspy_config, - ls_logger, - project_instance.project_root, - timeout=ls_timeout, - ) - return cast(SyncLanguageServer, ls) # TODO: Fix type - else: - return SyncLanguageServer.create( - multilspy_config, - ls_logger, - project_instance.project_root, - timeout=ls_timeout, - ) + return SolidLanguageServer.create( + multilspy_config, + ls_logger, + project_instance.project_root, + timeout=ls_timeout, + ) @click.command() @@ -799,7 +788,7 @@ class SerenaAgent: # project-specific instances, which will be initialized upon project activation self._active_project: Project | None = None self._active_project_root: str | None = None - self.language_server: SyncLanguageServer | None = None + self.language_server: SolidLanguageServer | None = None self.symbol_manager: SymbolManager | None = None self.memories_manager: MemoriesManager | None = None self.lines_read: LinesRead | None = None @@ -1145,7 +1134,7 @@ class Component(ABC): self.agent = agent @property - def language_server(self) -> SyncLanguageServer: + def language_server(self) -> SolidLanguageServer: assert self.agent.language_server is not None return self.agent.language_server diff --git a/src/serena/constants.py b/src/serena/constants.py index 6a277598..30a9518b 100644 --- a/src/serena/constants.py +++ b/src/serena/constants.py @@ -19,8 +19,4 @@ DEFAULT_MODES = ("interactive", "editing") PROJECT_TEMPLATE_FILE = str(_serena_pkg_path / "resources" / "project.template.yml") SELENA_CONFIG_TEMPLATE_FILE = str(_serena_pkg_path / "resources" / "serena_config.template.yml") -USE_SOLID_LSP = True -"""Whether to use Solid LSP instead of multilspy for language server operations. -Multilspy uses asyncio, which creates many problems like deadlocks and requires full process -isolation (and even then some issues remain). -""" +USE_PROCESS_ISOLATION = False diff --git a/src/serena/mcp.py b/src/serena/mcp.py index e2a0bc95..8666ef00 100644 --- a/src/serena/mcp.py +++ b/src/serena/mcp.py @@ -36,7 +36,7 @@ from serena.agent import ( show_fatal_exception_safe, ) from serena.config import RegisteredContext, SerenaAgentContext, SerenaAgentMode -from serena.constants import DEFAULT_CONTEXT, DEFAULT_MODES, USE_SOLID_LSP +from serena.constants import DEFAULT_CONTEXT, DEFAULT_MODES, USE_PROCESS_ISOLATION from serena.process_isolated_agent import ( ProcessIsolatedDashboard, ProcessIsolatedSerenaAgent, @@ -587,10 +587,9 @@ def start_mcp_server( project_file = project_file_arg if project_file_arg is not None else project mcp_factory: SerenaMCPFactory - if USE_SOLID_LSP: + if not USE_PROCESS_ISOLATION: mcp_factory = SerenaMCPFactorySingleProcess(context=context, project=project_file) else: - # using multilspy requires process isolation to prevent asyncio contamination mcp_factory = SerenaMCPFactoryWithProcessIsolation(context=context, project=project_file) # Use process isolation by default to prevent asyncio event loop contamination diff --git a/src/serena/symbol.py b/src/serena/symbol.py index e92ffdf8..5f5fbf14 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -9,9 +9,9 @@ from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Self, Union from sensai.util.string import ToStringMixin -from multilspy import SyncLanguageServer -from multilspy.language_server import ReferenceInSymbol as LSPReferenceInSymbol -from multilspy.multilspy_types import Position, SymbolKind, UnifiedSymbolInformation +from solidlsp import SolidLanguageServer +from solidlsp.ls import ReferenceInSymbol as LSPReferenceInSymbol +from solidlsp.ls_types import Position, SymbolKind, UnifiedSymbolInformation if TYPE_CHECKING: from .agent import SerenaAgent @@ -495,7 +495,7 @@ class ReferenceInSymbol(ToStringMixin): class SymbolManager: - def __init__(self, lang_server: SyncLanguageServer, agent: Union["SerenaAgent", None] = None) -> None: + def __init__(self, lang_server: SolidLanguageServer, agent: Union["SerenaAgent", None] = None) -> None: """ :param lang_server: the language server to use for symbol retrieval as well as editing operations. :param agent: the agent to use (only needed for marking files as modified). You can pass None if you don't @@ -504,7 +504,7 @@ class SymbolManager: self._lang_server = lang_server self.agent = agent - def set_language_server(self, lang_server: SyncLanguageServer) -> None: + def set_language_server(self, lang_server: SolidLanguageServer) -> None: """ Set the language server to use for symbol retrieval and editing operations. This is useful if you want to change the language server after initializing the SymbolManager. diff --git a/src/serena/util/inspection.py b/src/serena/util/inspection.py index 509da8e1..24c21719 100644 --- a/src/serena/util/inspection.py +++ b/src/serena/util/inspection.py @@ -3,8 +3,8 @@ import os from collections.abc import Generator from typing import TypeVar -from multilspy.multilspy_config import Language from serena.util.file_system import find_all_non_ignored_files +from solidlsp.ls_config import Language T = TypeVar("T") diff --git a/src/solidlsp/__init__.py b/src/solidlsp/__init__.py new file mode 100644 index 00000000..0378e2d6 --- /dev/null +++ b/src/solidlsp/__init__.py @@ -0,0 +1,2 @@ +# ruff: noqa +from .ls import SolidLanguageServer diff --git a/src/solidlsp/language_servers/clangd_language_server/clangd_language_server.py b/src/solidlsp/language_servers/clangd_language_server/clangd_language_server.py index 0ee5f471..4572de90 100644 --- a/src/solidlsp/language_servers/clangd_language_server/clangd_language_server.py +++ b/src/solidlsp/language_servers/clangd_language_server/clangd_language_server.py @@ -9,13 +9,12 @@ import pathlib import stat import threading -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils class ClangdLanguageServer(SolidLanguageServer): @@ -25,7 +24,7 @@ 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: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a ClangdLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -42,13 +41,13 @@ class ClangdLanguageServer(SolidLanguageServer): self.initialize_searcher_command_available = threading.Event() self.resolve_main_method_available = threading.Event() - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """ Setup runtime dependencies for ClangdLanguageServer. """ platform_id = PlatformUtils.get_platform_id() - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f: + with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json")) as f: d = json.load(f) del d["_description"] @@ -56,12 +55,12 @@ class ClangdLanguageServer(SolidLanguageServer): "linux-x64", "win-x64", "osx-arm64", - ], "Unsupported platform: " + platform_id.value + ], ( + "Unsupported platform: " + platform_id.value + ) runtime_dependencies = d["runtimeDependencies"] - runtime_dependencies = [ - dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value - ] + runtime_dependencies = [dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value] assert len(runtime_dependencies) == 1 # Select dependency matching the current platform dependency = next((dep for dep in runtime_dependencies if dep["platformId"] == platform_id.value), None) @@ -75,9 +74,7 @@ class ClangdLanguageServer(SolidLanguageServer): logger.log(f"Clangd executable not found at {clangd_executable_path}. Downloading from {clangd_url}", logging.INFO) os.makedirs(clangd_ls_dir, exist_ok=True) if dependency["archiveType"] == "zip": - FileUtils.download_and_extract_archive( - logger, clangd_url, clangd_ls_dir, dependency["archiveType"] - ) + FileUtils.download_and_extract_archive(logger, clangd_url, clangd_ls_dir, dependency["archiveType"]) else: raise RuntimeError(f"Unsupported archive type: {dependency['archiveType']}") if not os.path.exists(clangd_executable_path): @@ -93,7 +90,7 @@ class ClangdLanguageServer(SolidLanguageServer): """ Returns the initialize params for the clangd Language Server. """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r") as f: + with open(os.path.join(os.path.dirname(__file__), "initialize_params.json")) as f: d = json.load(f) del d["_description"] @@ -126,6 +123,7 @@ class ClangdLanguageServer(SolidLanguageServer): # Shutdown the LanguageServer on exit from scope # LanguageServer has been shutdown """ + def register_capability_handler(params): assert "registrations" in params for registration in params["registrations"]: @@ -175,7 +173,7 @@ class ClangdLanguageServer(SolidLanguageServer): assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 assert "completionProvider" in init_response["capabilities"] assert init_response["capabilities"]["completionProvider"] == { - "triggerCharacters": ['.', '<', '>', ':', '"', '/', '*'], + "triggerCharacters": [".", "<", ">", ":", '"', "/", "*"], "resolveProvider": False, } @@ -185,4 +183,3 @@ class ClangdLanguageServer(SolidLanguageServer): # set ready flag self.server_ready.set() self.server_ready.wait() - diff --git a/src/solidlsp/language_servers/dart_language_server/dart_language_server.py b/src/solidlsp/language_servers/dart_language_server/dart_language_server.py index 2a0c4e66..59575581 100644 --- a/src/solidlsp/language_servers/dart_language_server/dart_language_server.py +++ b/src/solidlsp/language_servers/dart_language_server/dart_language_server.py @@ -4,10 +4,10 @@ import os import pathlib import stat -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import FileUtils, PlatformUtils from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils class DartLanguageServer(SolidLanguageServer): @@ -19,7 +19,6 @@ class DartLanguageServer(SolidLanguageServer): """ Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ - executable_path = self.setup_runtime_dependencies(logger) super().__init__( config, @@ -29,17 +28,15 @@ class DartLanguageServer(SolidLanguageServer): "dart", ) - def setup_runtime_dependencies(self, logger: "MultilspyLogger") -> str: + def setup_runtime_dependencies(self, logger: "LanguageServerLogger") -> str: platform_id = PlatformUtils.get_platform_id() - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f: + with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json")) as f: d = json.load(f) del d["_description"] runtime_dependencies = d["runtimeDependencies"] - runtime_dependencies = [ - dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value - ] + runtime_dependencies = [dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value] assert len(runtime_dependencies) == 1 dependency = runtime_dependencies[0] @@ -49,24 +46,18 @@ class DartLanguageServer(SolidLanguageServer): if not os.path.exists(dart_ls_dir): os.makedirs(dart_ls_dir) - FileUtils.download_and_extract_archive( - logger, dependency["url"], dart_ls_dir, dependency["archiveType"] - ) - + FileUtils.download_and_extract_archive(logger, dependency["url"], dart_ls_dir, dependency["archiveType"]) assert os.path.exists(dart_executable_path) os.chmod(dart_executable_path, stat.S_IEXEC) return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2" - def _get_initialize_params(self, repository_absolute_path: str): """ Returns the initialize params for the Dart Language Server. """ - with open( - os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r" - ) as f: + with open(os.path.join(os.path.dirname(__file__), "initialize_params.json")) as f: d = json.load(f) del d["_description"] @@ -79,9 +70,7 @@ class DartLanguageServer(SolidLanguageServer): d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri() assert d["workspaceFolders"][0]["uri"] == "$uri" - d["workspaceFolders"][0]["uri"] = pathlib.Path( - repository_absolute_path - ).as_uri() + d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri() assert d["workspaceFolders"][0]["name"] == "$name" d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path) @@ -108,28 +97,20 @@ class DartLanguageServer(SolidLanguageServer): self.server.on_request("client/registerCapability", do_nothing) self.server.on_notification("language/status", do_nothing) self.server.on_notification("window/logMessage", window_log_message) - self.server.on_request( - "workspace/executeClientCommand", execute_client_command_handler - ) + self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) self.server.on_notification("$/progress", do_nothing) self.server.on_notification("textDocument/publishDiagnostics", do_nothing) self.server.on_notification("language/actionableNotification", do_nothing) - self.server.on_notification( - "experimental/serverStatus", check_experimental_status - ) + self.server.on_notification("experimental/serverStatus", check_experimental_status) - self.logger.log( - "Starting dart-language-server server process", logging.INFO - ) + self.logger.log("Starting dart-language-server server process", logging.INFO) 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, ) - init_response = self.server.send_request( - "initialize", initialize_params - ) + init_response = self.server.send_request("initialize", initialize_params) self.logger.log( f"Received initialize response from dart-language-server: {init_response}", logging.INFO, diff --git a/src/solidlsp/language_servers/eclipse_jdtls/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls/eclipse_jdtls.py index 1b92fc4d..a2453fae 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls/eclipse_jdtls.py @@ -15,14 +15,13 @@ from pathlib import PurePath from overrides import override -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_settings import MultilspySettings -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.settings import SolidLSPSettings +from solidlsp.ls_utils import FileUtils, PlatformUtils @dataclasses.dataclass @@ -46,19 +45,18 @@ class EclipseJDTLS(SolidLanguageServer): The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ 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) self.runtime_dependency_paths = runtime_dependency_paths # ws_dir is the workspace directory for the EclipseJDTLS server ws_dir = str( PurePath( - MultilspySettings.get_language_server_directory(), + SolidLSPSettings.get_language_server_directory(), "EclipseJDTLS", "workspaces", uuid.uuid4().hex, @@ -66,9 +64,7 @@ class EclipseJDTLS(SolidLanguageServer): ) # shared_cache_location is the global cache used by Eclipse JDTLS across all workspaces - shared_cache_location = str( - PurePath(MultilspySettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex") - ) + shared_cache_location = str(PurePath(SolidLSPSettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex")) jre_path = self.runtime_dependency_paths.jre_path lombok_jar_path = self.runtime_dependency_paths.lombok_jar_path @@ -138,7 +134,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") - + @override def is_ignored_dirname(self, dirname: str) -> bool: # Ignore common Java build directories from different build tools: @@ -148,22 +144,22 @@ class EclipseJDTLS(SolidLanguageServer): # - IntelliJ IDEA: out, .idea # - General: classes, dist, lib return super().is_ignored_dirname(dirname) or dirname in [ - "target", # Maven - "build", # Gradle - "bin", # Eclipse - "out", # IntelliJ IDEA - "classes", # General - "dist", # General - "lib" # General + "target", # Maven + "build", # Gradle + "bin", # Eclipse + "out", # IntelliJ IDEA + "classes", # General + "dist", # General + "lib", # General ] - def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> RuntimeDependencyPaths: + def setupRuntimeDependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> RuntimeDependencyPaths: """ Setup runtime dependencies for EclipseJDTLS. """ platformId = PlatformUtils.get_platform_id() - with open(str(PurePath(os.path.dirname(__file__), "runtime_dependencies.json")), "r", encoding="utf-8") as f: + with open(str(PurePath(os.path.dirname(__file__), "runtime_dependencies.json")), encoding="utf-8") as f: runtimeDependencies = json.load(f) del runtimeDependencies["_description"] @@ -192,9 +188,7 @@ class EclipseJDTLS(SolidLanguageServer): assert os.path.exists(gradle_path) dependency = runtimeDependencies["vscode-java"][platformId.value] - vscode_java_path = str( - PurePath(os.path.abspath(os.path.dirname(__file__)), "static", dependency["relative_extraction_path"]) - ) + vscode_java_path = str(PurePath(os.path.abspath(os.path.dirname(__file__)), "static", dependency["relative_extraction_path"])) os.makedirs(vscode_java_path, exist_ok=True) jre_home_path = str(PurePath(vscode_java_path, dependency["jre_home_path"])) jre_path = str(PurePath(vscode_java_path, dependency["jre_path"])) @@ -211,9 +205,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(logger, dependency["url"], vscode_java_path, dependency["archiveType"]) os.chmod(jre_path, stat.S_IEXEC) @@ -238,9 +230,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(logger, dependency["url"], intellicode_directory_path, dependency["archiveType"]) assert os.path.exists(intellicode_directory_path) assert os.path.exists(intellicode_jar_path) @@ -262,7 +252,7 @@ class EclipseJDTLS(SolidLanguageServer): Returns the initialize parameters for the EclipseJDTLS server. """ # Look into https://github.com/eclipse/eclipse.jdt.ls/blob/master/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java to understand all the options available - with open(str(PurePath(os.path.dirname(__file__), "initialize_params.json")), "r", encoding="utf-8") as f: + with open(str(PurePath(os.path.dirname(__file__), "initialize_params.json")), encoding="utf-8") as f: d: InitializeParams = json.load(f) del d["_description"] @@ -307,18 +297,12 @@ class EclipseJDTLS(SolidLanguageServer): for runtime in d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]: assert "name" in runtime assert "path" in runtime - assert os.path.exists( - runtime["path"] - ), f"Runtime required for eclipse_jdtls at path {runtime['path']} does not exist" + assert os.path.exists(runtime["path"]), f"Runtime required for eclipse_jdtls at path {runtime['path']} does not exist" assert d["initializationOptions"]["settings"]["java"]["import"]["gradle"]["home"] == "abs(static/gradle-7.3.3)" - d["initializationOptions"]["settings"]["java"]["import"]["gradle"][ - "home" - ] = self.runtime_dependency_paths.gradle_path + d["initializationOptions"]["settings"]["java"]["import"]["gradle"]["home"] = self.runtime_dependency_paths.gradle_path - d["initializationOptions"]["settings"]["java"]["import"]["gradle"]["java"][ - "home" - ] = self.runtime_dependency_paths.jre_path + d["initializationOptions"]["settings"]["java"]["import"]["gradle"]["java"]["home"] = self.runtime_dependency_paths.jre_path return d @@ -386,9 +370,7 @@ class EclipseJDTLS(SolidLanguageServer): self.server.notify.initialized({}) - self.server.notify.workspace_did_change_configuration( - {"settings": initialize_params["initializationOptions"]["settings"]} - ) + self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]}) self.intellicode_enable_command_available.wait() diff --git a/src/solidlsp/language_servers/gopls/gopls.py b/src/solidlsp/language_servers/gopls/gopls.py index a2ec2722..32ae5e5c 100644 --- a/src/solidlsp/language_servers/gopls/gopls.py +++ b/src/solidlsp/language_servers/gopls/gopls.py @@ -7,18 +7,18 @@ import threading from overrides import override -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger class Gopls(SolidLanguageServer): """ Provides Go specific instantiation of the LanguageServer class using gopls. """ - + @override def is_ignored_dirname(self, dirname: str) -> bool: # For Go projects, we should ignore: @@ -31,7 +31,7 @@ class Gopls(SolidLanguageServer): def _get_go_version(): """Get the installed Go version or None if not found.""" try: - result = subprocess.run(['go', 'version'], capture_output=True, text=True) + result = subprocess.run(["go", "version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: @@ -42,7 +42,7 @@ class Gopls(SolidLanguageServer): def _get_gopls_version(): """Get the installed gopls version or None if not found.""" try: - result = subprocess.run(['gopls', 'version'], capture_output=True, text=True) + result = subprocess.run(["gopls", "version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: @@ -57,8 +57,10 @@ class Gopls(SolidLanguageServer): """ go_version = cls._get_go_version() if not go_version: - raise RuntimeError("Go is not installed. Please install Go from https://golang.org/doc/install and make sure it is added to your PATH.") - + raise RuntimeError( + "Go is not installed. Please install Go from https://golang.org/doc/install and make sure it is added to your PATH." + ) + gopls_version = cls._get_gopls_version() if not gopls_version: raise RuntimeError( @@ -66,12 +68,12 @@ class Gopls(SolidLanguageServer): "Please install gopls as described in https://pkg.go.dev/golang.org/x/tools/gopls#section-readme\n\n" "After installation, make sure it is added to your PATH (it might be installed in a different location than Go)." ) - + return True - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): self.setup_runtime_dependency() - + super().__init__( config, logger, @@ -86,7 +88,7 @@ class Gopls(SolidLanguageServer): """ Returns the initialize params for the TypeScript Language Server. """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] @@ -108,6 +110,7 @@ class Gopls(SolidLanguageServer): def _start_server(self): """Start gopls server process""" + def register_capability_handler(params): return diff --git a/src/solidlsp/language_servers/intelephense/intelephense.py b/src/solidlsp/language_servers/intelephense/intelephense.py index 9b6eb955..a02f9d18 100644 --- a/src/solidlsp/language_servers/intelephense/intelephense.py +++ b/src/solidlsp/language_servers/intelephense/intelephense.py @@ -12,28 +12,28 @@ from time import sleep from overrides import override -from multilspy.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import PlatformUtils, PlatformId from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import PlatformId, PlatformUtils class Intelephense(SolidLanguageServer): """ Provides PHP specific instantiation of the LanguageServer class using Intelephense. """ - + @override def is_ignored_dirname(self, dirname: str) -> bool: # For PHP projects, we should ignore: # - vendor: third-party dependencies managed by Composer # - node_modules: if the project has JavaScript components # - cache: commonly used for caching - return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "cache"] + return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "cache"] - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """ Setup runtime dependencies for Intelephense. """ @@ -50,17 +50,17 @@ class Intelephense(SolidLanguageServer): ] assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for multilspy PHP at the moment" - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] runtime_dependencies = d.get("runtimeDependencies", []) intelephense_ls_dir = os.path.join(os.path.dirname(__file__), "static", "php-lsp") - + # Verify both node and npm are installed - is_node_installed = shutil.which('node') is not None + is_node_installed = shutil.which("node") is not None assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again." - is_npm_installed = shutil.which('npm') is not None + is_npm_installed = shutil.which("npm") is not None assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again." # Install intelephense if not already installed @@ -75,11 +75,12 @@ class Intelephense(SolidLanguageServer): check=True, cwd=intelephense_ls_dir, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL + stderr=subprocess.DEVNULL, ) else: # On Unix-like systems, run as non-root user import pwd + user = pwd.getpwuid(os.getuid()).pw_name subprocess.run( dependency["command"], @@ -88,32 +89,26 @@ class Intelephense(SolidLanguageServer): user=user, cwd=intelephense_ls_dir, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL + stderr=subprocess.DEVNULL, ) - + intelephense_executable_path = os.path.join(intelephense_ls_dir, "node_modules", ".bin", "intelephense") assert os.path.exists(intelephense_executable_path), "intelephense executable not found. Please install intelephense and try again." - + return f"{intelephense_executable_path} --stdio" - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): # Setup runtime dependencies before initializing intelephense_cmd = self.setup_runtime_dependencies(logger, config) - - super().__init__( - config, - logger, - repository_root_path, - ProcessLaunchInfo(cmd=intelephense_cmd, cwd=repository_root_path), - "php" - ) + + super().__init__(config, logger, repository_root_path, ProcessLaunchInfo(cmd=intelephense_cmd, cwd=repository_root_path), "php") self.request_id = 0 def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the TypeScript Language Server. """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] @@ -135,6 +130,7 @@ class Intelephense(SolidLanguageServer): def _start_server(self): """Start Intelephense server process""" + def register_capability_handler(params): return @@ -184,7 +180,7 @@ class Intelephense(SolidLanguageServer): # The sleeping doesn't seem to be needed on all systems sleep(1) return super()._send_references_request(relative_file_path, line, column) - + @override def _send_definition_request(self, definition_params: DefinitionParams): # TODO: same as above, also only a problem if the definition is in another file diff --git a/src/solidlsp/language_servers/jedi_language_server/jedi_server.py b/src/solidlsp/language_servers/jedi_language_server/jedi_server.py index 9a4dbc8a..a0ea2030 100644 --- a/src/solidlsp/language_servers/jedi_language_server/jedi_server.py +++ b/src/solidlsp/language_servers/jedi_language_server/jedi_server.py @@ -9,11 +9,11 @@ import pathlib from overrides import override -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger class JediServer(SolidLanguageServer): @@ -21,7 +21,7 @@ class JediServer(SolidLanguageServer): Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a JediServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -32,7 +32,7 @@ class JediServer(SolidLanguageServer): ProcessLaunchInfo(cmd="jedi-language-server", cwd=repository_root_path), "python", ) - + @override def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"] @@ -41,7 +41,7 @@ class JediServer(SolidLanguageServer): """ Returns the initialize params for the Jedi Language Server. """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] diff --git a/src/solidlsp/language_servers/kotlin_language_server/kotlin_language_server.py b/src/solidlsp/language_servers/kotlin_language_server/kotlin_language_server.py index 88e71518..30315357 100644 --- a/src/solidlsp/language_servers/kotlin_language_server/kotlin_language_server.py +++ b/src/solidlsp/language_servers/kotlin_language_server/kotlin_language_server.py @@ -9,13 +9,12 @@ import os import pathlib import stat -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils @dataclasses.dataclass @@ -23,6 +22,7 @@ class KotlinRuntimeDependencyPaths: """ Stores the paths to the runtime dependencies of Kotlin Language Server """ + java_path: str java_home_path: str kotlin_executable_path: str @@ -33,19 +33,19 @@ class KotlinLanguageServer(SolidLanguageServer): Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ 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) self.runtime_dependency_paths = runtime_dependency_paths - + # Create command to execute the Kotlin Language Server script cmd = f'"{self.runtime_dependency_paths.kotlin_executable_path}"' - + # Set environment variables including JAVA_HOME proc_env = {"JAVA_HOME": self.runtime_dependency_paths.java_home_path} - + super().__init__( config, logger, @@ -54,84 +54,82 @@ class KotlinLanguageServer(SolidLanguageServer): "kotlin", ) - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> KotlinRuntimeDependencyPaths: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> KotlinRuntimeDependencyPaths: """ Setup runtime dependencies for Kotlin Language Server. """ platform_id = PlatformUtils.get_platform_id() # Verify platform support - assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), "Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment" + assert ( + platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-") + ), "Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment" # Load dependency information - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] - + kotlin_dependency = d["runtimeDependency"] java_dependency = d["java"][platform_id.value] # Setup paths for dependencies static_dir = os.path.join(os.path.dirname(__file__), "static") os.makedirs(static_dir, exist_ok=True) - + # Setup Java paths java_dir = os.path.join(static_dir, "java") os.makedirs(java_dir, exist_ok=True) - + java_home_path = os.path.join(java_dir, java_dependency["java_home_path"]) java_path = os.path.join(java_dir, java_dependency["java_path"]) - + # 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"] - ) + FileUtils.download_and_extract_archive(logger, java_dependency["url"], java_dir, java_dependency["archiveType"]) # Make Java executable if not platform_id.value.startswith("win-"): os.chmod(java_path, 0o755) - + assert os.path.exists(java_path), f"Java executable not found at {java_path}" - + # Setup Kotlin Language Server paths kotlin_ls_dir = os.path.join(static_dir, "server") - + # Get platform-specific executable script path if platform_id.value.startswith("win-"): kotlin_script = os.path.join(kotlin_ls_dir, "bin", "kotlin-language-server.bat") else: kotlin_script = os.path.join(kotlin_ls_dir, "bin", "kotlin-language-server") - + # 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"] - ) - + FileUtils.download_and_extract_archive(logger, kotlin_dependency["url"], static_dir, kotlin_dependency["archiveType"]) + # Make script executable on Unix platforms if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"): - os.chmod(kotlin_script, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) - + os.chmod( + kotlin_script, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH + ) + # 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) else: raise FileNotFoundError(f"Kotlin Language Server script not found at {kotlin_script}") - + return KotlinRuntimeDependencyPaths( - java_path=java_path, - java_home_path=java_home_path, - kotlin_executable_path=kotlin_executable_path + java_path=java_path, java_home_path=java_home_path, kotlin_executable_path=kotlin_executable_path ) def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the Kotlin Language Server. """ - with open(str(pathlib.PurePath(os.path.dirname(__file__), "initialize_params.json")), "r", encoding="utf-8") as f: + with open(str(pathlib.PurePath(os.path.dirname(__file__), "initialize_params.json")), encoding="utf-8") as f: d: InitializeParams = json.load(f) del d["_description"] @@ -152,8 +150,8 @@ class KotlinLanguageServer(SolidLanguageServer): d["initializationOptions"]["workspaceFolders"] = [pathlib.Path(repository_absolute_path).as_uri()] assert ( - d["workspaceFolders"] - == '[\n {\n "uri": pathlib.Path(repository_absolute_path).as_uri(),\n "name": os.path.basename(repository_absolute_path),\n }\n ]' + d["workspaceFolders"] + == '[\n {\n "uri": pathlib.Path(repository_absolute_path).as_uri(),\n "name": os.path.basename(repository_absolute_path),\n }\n ]' ) d["workspaceFolders"] = [ { @@ -168,6 +166,7 @@ class KotlinLanguageServer(SolidLanguageServer): """ Starts the Kotlin Language Server """ + def execute_client_command_handler(params): return [] diff --git a/src/solidlsp/language_servers/omnisharp/omnisharp.py b/src/solidlsp/language_servers/omnisharp/omnisharp.py index a449a985..0a0216cd 100644 --- a/src/solidlsp/language_servers/omnisharp/omnisharp.py +++ b/src/solidlsp/language_servers/omnisharp/omnisharp.py @@ -8,17 +8,17 @@ import os import pathlib import stat import threading -from typing import Iterable +from collections.abc import Iterable from overrides import override -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_exceptions import MultilspyException -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import FileUtils, PlatformUtils, PlatformId, DotnetVersion from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_exceptions import LanguageServerException +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils def breadth_first_file_scan(root) -> Iterable[str]: @@ -28,7 +28,7 @@ def breadth_first_file_scan(root) -> Iterable[str]: """ dirs = [root] # while we has dirs to scan - while len(dirs): + while dirs: next_dirs = [] for parent in dirs: # scan each dir @@ -59,7 +59,7 @@ class OmniSharp(SolidLanguageServer): Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates an OmniSharp instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -68,7 +68,7 @@ class OmniSharp(SolidLanguageServer): slnfilename = find_least_depth_sln_file(repository_root_path) if slnfilename is None: logger.log("No *.sln file found in repository", logging.ERROR) - raise MultilspyException("No SLN file found in repository") + raise LanguageServerException("No SLN file found in repository") cmd = " ".join( [ @@ -102,14 +102,12 @@ class OmniSharp(SolidLanguageServer): "formattingOptions:indentationSize=4", ] ) - super().__init__( - config, logger, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp" - ) + super().__init__(config, logger, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp") self.server_ready = threading.Event() self.definition_available = threading.Event() self.references_available = threading.Event() - + @override def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["bin", "obj"] @@ -118,7 +116,7 @@ class OmniSharp(SolidLanguageServer): """ Returns the initialize params for the Omnisharp Language Server. """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] @@ -138,14 +136,14 @@ class OmniSharp(SolidLanguageServer): return d - def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> tuple[str, str]: + def setupRuntimeDependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> tuple[str, str]: """ Setup runtime dependencies for OmniSharp. """ platform_id = PlatformUtils.get_platform_id() dotnet_version = PlatformUtils.get_dotnet_version() - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] @@ -156,7 +154,7 @@ class OmniSharp(SolidLanguageServer): assert dotnet_version in [ DotnetVersion.V6, DotnetVersion.V7, - DotnetVersion.V8 + DotnetVersion.V8, ], "Only dotnet version 6 and 7 are supported in multilspy at the moment" # TODO: Do away with this assumption @@ -165,13 +163,11 @@ class OmniSharp(SolidLanguageServer): dotnet_version = DotnetVersion.V6 runtime_dependencies = d["runtimeDependencies"] - runtime_dependencies = [ - dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value - ] + runtime_dependencies = [dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value] runtime_dependencies = [ dependency for dependency in runtime_dependencies - if not ("dotnet_version" in dependency) or dependency["dotnet_version"] == dotnet_version.value + if "dotnet_version" not in dependency or dependency["dotnet_version"] == dotnet_version.value ] assert len(runtime_dependencies) == 2 runtime_dependencies = { @@ -185,9 +181,7 @@ class OmniSharp(SolidLanguageServer): omnisharp_ls_dir = os.path.join(os.path.dirname(__file__), "static", "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(logger, 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, stat.S_IEXEC) @@ -195,12 +189,8 @@ class OmniSharp(SolidLanguageServer): razor_omnisharp_ls_dir = os.path.join(os.path.dirname(__file__), "static", "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" - ) - razor_omnisharp_dll_path = os.path.join( - razor_omnisharp_ls_dir, runtime_dependencies["RazorOmnisharp"]["dll_path"] - ) + FileUtils.download_and_extract_archive(logger, 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) return omnisharp_executable_path, razor_omnisharp_dll_path @@ -373,20 +363,12 @@ class OmniSharp(SolidLanguageServer): ) init_response = self.server.send.initialize(initialize_params) self.server.notify.initialized({}) - with open(os.path.join(os.path.dirname(__file__), "workspace_did_change_configuration.json"), "r", encoding="utf-8") as f: - self.server.notify.workspace_did_change_configuration({ - "settings": json.load(f) - }) + with open(os.path.join(os.path.dirname(__file__), "workspace_did_change_configuration.json"), encoding="utf-8") as f: + self.server.notify.workspace_did_change_configuration({"settings": json.load(f)}) assert "capabilities" in init_response - if ( - "definitionProvider" in init_response["capabilities"] - and init_response["capabilities"]["definitionProvider"] - ): + if "definitionProvider" in init_response["capabilities"] and init_response["capabilities"]["definitionProvider"]: self.definition_available.set() - if ( - "referencesProvider" in init_response["capabilities"] - and init_response["capabilities"]["referencesProvider"] - ): + if "referencesProvider" in init_response["capabilities"] and init_response["capabilities"]["referencesProvider"]: self.references_available.set() self.definition_available.wait() diff --git a/src/solidlsp/language_servers/pyright_language_server/pyright_server.py b/src/solidlsp/language_servers/pyright_language_server/pyright_server.py index b3490502..64dbd574 100644 --- a/src/solidlsp/language_servers/pyright_language_server/pyright_server.py +++ b/src/solidlsp/language_servers/pyright_language_server/pyright_server.py @@ -10,11 +10,11 @@ import threading from overrides import override -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger class PyrightServer(SolidLanguageServer): @@ -22,7 +22,8 @@ class PyrightServer(SolidLanguageServer): Provides Python specific instantiation of the LanguageServer class using Pyright. Contains various configurations and settings specific to Python. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a PyrightServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. @@ -50,7 +51,7 @@ class PyrightServer(SolidLanguageServer): Returns the initialize params for the Pyright Language Server. """ # Create basic initialization parameters - initialize_params: InitializeParams = { # type: ignore + initialize_params: InitializeParams = { # type: ignore "processId": os.getpid(), "rootPath": repository_absolute_path, "rootUri": pathlib.Path(repository_absolute_path).as_uri(), diff --git a/src/solidlsp/language_servers/rust_analyzer/rust_analyzer.py b/src/solidlsp/language_servers/rust_analyzer/rust_analyzer.py index eb072c49..a21768ef 100644 --- a/src/solidlsp/language_servers/rust_analyzer/rust_analyzer.py +++ b/src/solidlsp/language_servers/rust_analyzer/rust_analyzer.py @@ -11,13 +11,12 @@ import threading from overrides import override -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import FileUtils -from multilspy.multilspy_utils import PlatformUtils from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils class RustAnalyzer(SolidLanguageServer): @@ -25,7 +24,7 @@ class RustAnalyzer(SolidLanguageServer): Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -46,13 +45,13 @@ class RustAnalyzer(SolidLanguageServer): def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["target"] - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """ Setup runtime dependencies for rust_analyzer. """ platform_id = PlatformUtils.get_platform_id() - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] @@ -62,9 +61,7 @@ class RustAnalyzer(SolidLanguageServer): # ], "Only linux-x64 and win-x64 platform is supported for in multilspy at the moment" runtime_dependencies = d["runtimeDependencies"] - runtime_dependencies = [ - dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value - ] + runtime_dependencies = [dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value] assert len(runtime_dependencies) == 1 dependency = runtime_dependencies[0] @@ -73,13 +70,9 @@ class RustAnalyzer(SolidLanguageServer): if not os.path.exists(rustanalyzer_ls_dir): os.makedirs(rustanalyzer_ls_dir) if dependency["archiveType"] == "gz": - FileUtils.download_and_extract_archive( - logger, dependency["url"], rustanalyzer_executable_path, dependency["archiveType"] - ) + FileUtils.download_and_extract_archive(logger, dependency["url"], rustanalyzer_executable_path, dependency["archiveType"]) else: - FileUtils.download_and_extract_archive( - logger, dependency["url"], rustanalyzer_ls_dir, dependency["archiveType"] - ) + FileUtils.download_and_extract_archive(logger, dependency["url"], rustanalyzer_ls_dir, dependency["archiveType"]) assert os.path.exists(rustanalyzer_executable_path) os.chmod(rustanalyzer_executable_path, stat.S_IEXEC) @@ -89,7 +82,7 @@ class RustAnalyzer(SolidLanguageServer): """ Returns the initialize params for the Rust Analyzer Language Server. """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] diff --git a/src/solidlsp/language_servers/solargraph/solargraph.py b/src/solidlsp/language_servers/solargraph/solargraph.py index d036ed1e..07cc1b38 100644 --- a/src/solidlsp/language_servers/solargraph/solargraph.py +++ b/src/solidlsp/language_servers/solargraph/solargraph.py @@ -12,11 +12,11 @@ import subprocess import threading from typing import override -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger class Solargraph(SolidLanguageServer): @@ -25,7 +25,7 @@ class Solargraph(SolidLanguageServer): Contains various configurations and settings specific to Ruby. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a Solargraph instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. @@ -47,12 +47,11 @@ class Solargraph(SolidLanguageServer): def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["vendor"] - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig, repository_root_path: str) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig, repository_root_path: str) -> str: """ Setup runtime dependencies for Solargraph. """ - - with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] @@ -64,38 +63,40 @@ class Solargraph(SolidLanguageServer): ruby_version = result.stdout.strip() logger.log(f"Ruby version: {ruby_version}", logging.INFO) except subprocess.CalledProcessError as e: - raise RuntimeError(f"Error checking for Ruby installation: {e.stderr}") - except FileNotFoundError: - raise RuntimeError("Ruby is not installed. Please install Ruby before continuing.") + raise RuntimeError(f"Error checking for Ruby installation: {e.stderr}") from e + except FileNotFoundError as e: + raise RuntimeError("Ruby is not installed. Please install Ruby before continuing.") from e # Check if solargraph is installed try: - result = subprocess.run(["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path) + result = subprocess.run( + ["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) subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path) - + # Get the gem executable path directly result = subprocess.run(["gem", "which", "solargraph"], check=True, capture_output=True, text=True, cwd=repository_root_path) gem_path = result.stdout.strip() bin_dir = os.path.join(os.path.dirname(os.path.dirname(gem_path)), "bin") executable_path = os.path.join(bin_dir, "solargraph") - + if not os.path.exists(executable_path): raise RuntimeError(f"Solargraph executable not found at {executable_path}") - + # Ensure the executable has the right permissions os.chmod(executable_path, os.stat(executable_path).st_mode | stat.S_IEXEC) return executable_path except subprocess.CalledProcessError as e: - raise RuntimeError(f"Failed to check or install Solargraph. {e.stderr}") + raise RuntimeError(f"Failed to check or install Solargraph. {e.stderr}") from e def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the Solargraph Language Server. """ - with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f: + with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f: d = json.load(f) del d["_description"] diff --git a/src/solidlsp/language_servers/typescript_language_server/typescript_language_server.py b/src/solidlsp/language_servers/typescript_language_server/typescript_language_server.py index e17a0987..dd1d230a 100644 --- a/src/solidlsp/language_servers/typescript_language_server/typescript_language_server.py +++ b/src/solidlsp/language_servers/typescript_language_server/typescript_language_server.py @@ -13,22 +13,22 @@ from time import sleep from overrides import override -from multilspy.lsp_protocol_handler.lsp_types import InitializeParams -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo -from multilspy.multilspy_config import MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import PlatformId, PlatformUtils from solidlsp.ls import SolidLanguageServer +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams +from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import PlatformId, PlatformUtils # Platform-specific imports -if os.name != 'nt': # Unix-like systems +if os.name != "nt": # Unix-like systems import pwd else: # Dummy pwd module for Windows class pwd: @staticmethod def getpwuid(uid): - return type('obj', (), {'pw_name': os.environ.get('USERNAME', 'unknown')})() + return type("obj", (), {"pw_name": os.environ.get("USERNAME", "unknown")})() # Conditionally import pwd module (Unix-only) @@ -41,7 +41,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -65,7 +65,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): "coverage", ] - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """ Setup runtime dependencies for TypeScript Language Server. """ @@ -91,9 +91,9 @@ class TypeScriptLanguageServer(SolidLanguageServer): tsserver_executable_path = os.path.join(tsserver_ls_dir, "typescript-language-server") # Verify both node and npm are installed - is_node_installed = shutil.which('node') is not None + is_node_installed = shutil.which("node") is not None assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again." - is_npm_installed = shutil.which('npm') is not None + is_npm_installed = shutil.which("npm") is not None assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again." # Install typescript and typescript-language-server if not already installed @@ -108,7 +108,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): check=True, cwd=tsserver_ls_dir, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL + stderr=subprocess.DEVNULL, ) else: # On Unix-like systems, run as non-root user @@ -120,12 +120,14 @@ class TypeScriptLanguageServer(SolidLanguageServer): user=user, cwd=tsserver_ls_dir, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL + stderr=subprocess.DEVNULL, ) - + tsserver_executable_path = os.path.join(tsserver_ls_dir, "node_modules", ".bin", "typescript-language-server") - assert os.path.exists(tsserver_executable_path), "typescript-language-server executable not found. Please install typescript-language-server and try again." + assert os.path.exists( + tsserver_executable_path + ), "typescript-language-server executable not found. Please install typescript-language-server and try again." return f"{tsserver_executable_path} --stdio" def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: @@ -184,8 +186,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): def window_log_message(msg): self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) - - + def check_experimental_status(params): """ Also listen for experimental/serverStatus as a backup signal @@ -215,8 +216,8 @@ class TypeScriptLanguageServer(SolidLanguageServer): assert init_response["capabilities"]["textDocumentSync"] == 2 assert "completionProvider" in init_response["capabilities"] assert init_response["capabilities"]["completionProvider"] == { - "triggerCharacters": ['.', '"', "'", '/', '@', '<'], - "resolveProvider": True + "triggerCharacters": [".", '"', "'", "/", "@", "<"], + "resolveProvider": True, } self.server.notify.initialized({}) diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 9981ed1e..426d471e 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -1,3 +1,5 @@ +import dataclasses +import hashlib import json import logging import os @@ -5,33 +7,71 @@ import pathlib import pickle import re import threading -from abc import abstractmethod, ABC +from abc import ABC, abstractmethod from collections import defaultdict +from collections.abc import Iterator from contextlib import contextmanager from copy import copy from pathlib import Path, PurePath -from typing import Dict, Iterator, List, Optional, Tuple, Union, cast, Self +from typing import Self, Union, cast import pathspec import tqdm -from multilspy import multilspy_types -from multilspy.language_server import LSPFileBuffer, GenericDocumentSymbol, ReferenceInSymbol -from multilspy.lsp_protocol_handler import lsp_types -from multilspy.lsp_protocol_handler import lsp_types as LSPTypes -from multilspy.lsp_protocol_handler.lsp_constants import LSPConstants -from multilspy.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink, SymbolKind -from multilspy.lsp_protocol_handler.server import ( +from serena.text_utils import MatchedConsecutiveLines, search_files +from solidlsp import ls_types +from solidlsp.ls_handler import SolidLanguageServerHandler +from solidlsp.lsp_protocol_handler import lsp_types +from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes +from solidlsp.lsp_protocol_handler.lsp_constants import LSPConstants +from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink, SymbolKind +from solidlsp.lsp_protocol_handler.server import ( Error, ProcessLaunchInfo, StringDict, ) -from multilspy.multilspy_config import Language, MultilspyConfig -from multilspy.multilspy_exceptions import MultilspyException -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_utils import FileUtils, PathUtils, TextUtils -from serena.text_utils import MatchedConsecutiveLines, search_files -from solidlsp.ls_handler import SolidLanguageServerHandler +from solidlsp.ls_config import Language, LanguageServerConfig +from solidlsp.ls_exceptions import LanguageServerException +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PathUtils, TextUtils + +GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, ls_types.UnifiedSymbolInformation] + + +@dataclasses.dataclass(kw_only=True) +class ReferenceInSymbol: + """A symbol retrieved when requesting reference to a symbol, together with the location of the reference""" + + symbol: ls_types.UnifiedSymbolInformation + line: int + character: int + + +@dataclasses.dataclass +class LSPFileBuffer: + """ + This class is used to store the contents of an open LSP file in memory. + """ + + # uri of the file + uri: str + + # The contents of the file + contents: str + + # The version of the file + version: int + + # The language id of the file + language_id: str + + # reference count of the file + ref_count: int + + content_hash: str = "" + + def __post_init__(self): + self.content_hash = hashlib.md5(self.contents.encode("utf-8")).hexdigest() class SolidLanguageServer(ABC): @@ -46,10 +86,12 @@ class SolidLanguageServer(ABC): A language-specific condition for directories that should always be ignored. For example, venv in Python and node_modules in JS/TS should be ignored always. """ - return dirname.startswith('.') + return dirname.startswith(".") @classmethod - def create(cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str, timeout: Optional[float] = None) -> "SolidLanguageServer": + def create( + cls, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, timeout: float | None = None + ) -> "SolidLanguageServer": """ Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language. @@ -66,6 +108,7 @@ class SolidLanguageServer(ABC): from solidlsp.language_servers.pyright_language_server.pyright_server import ( PyrightServer, ) + return PyrightServer(config, logger, repository_root_path) # It used to be jedi, but pyright is a bit faster, and also more actively maintained # Keeping the previous code for reference @@ -79,61 +122,71 @@ class SolidLanguageServer(ABC): from solidlsp.language_servers.eclipse_jdtls.eclipse_jdtls import ( EclipseJDTLS, ) + return EclipseJDTLS(config, logger, repository_root_path) elif config.code_language == Language.KOTLIN: from solidlsp.language_servers.kotlin_language_server.kotlin_language_server import ( KotlinLanguageServer, ) + return KotlinLanguageServer(config, logger, repository_root_path) elif config.code_language == Language.RUST: from solidlsp.language_servers.rust_analyzer.rust_analyzer import ( RustAnalyzer, ) + return RustAnalyzer(config, logger, repository_root_path) elif config.code_language == Language.CSHARP: from solidlsp.language_servers.omnisharp.omnisharp import OmniSharp + return OmniSharp(config, logger, repository_root_path) elif config.code_language in [Language.TYPESCRIPT, Language.JAVASCRIPT]: from solidlsp.language_servers.typescript_language_server.typescript_language_server import ( TypeScriptLanguageServer, ) + return TypeScriptLanguageServer(config, logger, repository_root_path) elif config.code_language == Language.GO: from solidlsp.language_servers.gopls.gopls import Gopls + return Gopls(config, logger, repository_root_path) elif config.code_language == Language.RUBY: from solidlsp.language_servers.solargraph.solargraph import Solargraph + return Solargraph(config, logger, repository_root_path) elif config.code_language == Language.DART: from solidlsp.language_servers.dart_language_server.dart_language_server import DartLanguageServer + return DartLanguageServer(config, logger, repository_root_path) elif config.code_language == Language.CPP: from solidlsp.language_servers.clangd_language_server.clangd_language_server import ClangdLanguageServer + return ClangdLanguageServer(config, logger, repository_root_path) elif config.code_language == Language.PHP: from solidlsp.language_servers.intelephense.intelephense import Intelephense + return Intelephense(config, logger, repository_root_path) else: logger.log(f"Language {config.code_language} is not supported", logging.ERROR) - raise MultilspyException(f"Language {config.code_language} is not supported") + raise LanguageServerException(f"Language {config.code_language} is not supported") def __init__( - self, - config: MultilspyConfig, - logger: MultilspyLogger, - repository_root_path: str, - process_launch_info: ProcessLaunchInfo, - language_id: str, + self, + config: LanguageServerConfig, + logger: LanguageServerLogger, + repository_root_path: str, + process_launch_info: ProcessLaunchInfo, + language_id: str, ): """ Initializes a LanguageServer instance. @@ -150,14 +203,19 @@ class SolidLanguageServer(ABC): """ 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) + self.logger.log( + f"Creating language server instance for {repository_root_path=} with {language_id=} and process launch info: {process_launch_info}", + logging.DEBUG, + ) self.language_id = language_id - self.open_file_buffers: Dict[str, LSPFileBuffer] = {} + self.open_file_buffers: dict[str, LSPFileBuffer] = {} self.language = Language(language_id) # load cache first to prevent any racing conditions due to asyncio stuff - self._document_symbols_cache: dict[str, Tuple[str, Tuple[List[multilspy_types.UnifiedSymbolInformation], List[multilspy_types.UnifiedSymbolInformation]]]] = {} + self._document_symbols_cache: dict[ + str, tuple[str, tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]] + ] = {} """Maps file paths to a tuple of (file_content_hash, result_of_request_document_symbols)""" self._cache_lock = threading.Lock() self._cache_has_changed: bool = False @@ -166,36 +224,35 @@ class SolidLanguageServer(ABC): self.server_started = False self.completions_available = threading.Event() if config.trace_lsp_communication: + def logging_fn(source: str, target: str, msg: StringDict | str): self.logger.log(f"LSP: {source} -> {target}: {str(msg)[:90]}...", self.logger.logger.level) + else: logging_fn = None - # 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) + self.logger.log( + f"Creating language server instance with {language_id=} and process launch info: {process_launch_info}", logging.DEBUG + ) self.server = SolidLanguageServerHandler( process_launch_info, logger=logging_fn, start_independent_lsp_process=config.start_independent_lsp_process, ) - # Set up the pathspec matcher for the ignored paths # for all absolute paths in ignored_paths, convert them to relative paths processed_patterns = [] for pattern in set(config.ignored_paths): # Normalize separators (pathspec expects forward slashes) - pattern = pattern.replace(os.path.sep, '/') + 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) # Create a pathspec matcher from the processed patterns - self._ignore_spec = pathspec.PathSpec.from_lines( - pathspec.patterns.GitWildMatchPattern, - processed_patterns - ) + self._ignore_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, processed_patterns) self._server_context = None @@ -247,12 +304,12 @@ class SolidLanguageServer(ABC): # Use pathspec for gitignore-style pattern matching # Normalize path separators for pathspec (it expects forward slashes) - normalized_path = str(rel_path).replace(os.path.sep, '/') + normalized_path = str(rel_path).replace(os.path.sep, "/") # pathspec can't handle the matching of directories if they don't end with a slash! # see https://github.com/cpburnz/python-pathspec/issues/89 - if os.path.isdir(os.path.join(self.repository_root_path, normalized_path)) and not normalized_path.endswith('/'): - normalized_path = normalized_path + '/' + if os.path.isdir(os.path.join(self.repository_root_path, normalized_path)) and not normalized_path.endswith("/"): + normalized_path = normalized_path + "/" # Use the pathspec matcher to check if the path matches any ignore pattern if self.get_ignore_spec().match_file(normalized_path): @@ -271,7 +328,6 @@ class SolidLanguageServer(ABC): self.logger.log(f"Initiating final robust shutdown with a {timeout}s timeout...", logging.INFO) process = self.server.process - reader_tasks = list(self.server.tasks.values()) # --- Main Shutdown Logic --- # Stage 1: Graceful Termination Request @@ -281,50 +337,11 @@ class SolidLanguageServer(ABC): if process.stdin and not process.stdin.is_closing(): process.stdin.close() except Exception: - pass # Ignore errors here, we are proceeding to terminate anyway. + pass # Ignore errors here, we are proceeding to terminate anyway. # Stage 2: Terminate and Concurrently Drain stdout/stderr process.terminate() - """ - except asyncio.TimeoutError: - # Stage 3: Forceful Kill - self.logger.log("Graceful termination failed. Forcefully killing process...", logging.WARNING) - if self.server.is_running(): - try: - process.kill() - # Wait for the killed process to be reaped by the OS. - await process.wait() - except Exception as e: - self.logger.log(f"Error during forceful kill: {e}", logging.ERROR) - except Exception as e: - self.logger.log(f"An unexpected error occurred during shutdown logic: {e}", logging.ERROR) - - finally: - # === STAGE 4: EXPLICIT TASK & PIPE CLEANUP === - self.logger.log("Performing final task cancellation and pipe handle cleanup...", logging.DEBUG) - - # 1. Cancel any lingering reader tasks. - for task in reader_tasks: - if not task.done(): - task.cancel() - # Wait for cancellations to complete. - await asyncio.gather(*reader_tasks, return_exceptions=True) - self.server.tasks = {} - - # 2. Explicitly close each pipe to release OS handles. - for pipe in [process.stdin, process.stdout, process.stderr]: - if pipe and hasattr(pipe, 'is_closing') and not pipe.is_closing(): - try: - pipe.close() - except Exception: - pass - - # 3. Null out the process object in the handler. - self.server.process = None - self.logger.log("Shutdown sequence fully finished.", logging.DEBUG) - """ - @contextmanager def start_server(self) -> Iterator["SolidLanguageServer"]: self.start() @@ -351,7 +368,7 @@ class SolidLanguageServer(ABC): "open_file called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) uri = pathlib.Path(absolute_file_path).as_uri() @@ -393,10 +410,10 @@ class SolidLanguageServer(ABC): del self.open_file_buffers[uri] def insert_text_at_position( - self, relative_file_path: str, line: int, column: int, text_to_be_inserted: str - ) -> multilspy_types.Position: + self, relative_file_path: str, line: int, column: int, text_to_be_inserted: str + ) -> ls_types.Position: """ - Insert text at the given line and column in the given file and return + Insert text at the given line and column in the given file and return the updated cursor position after inserting the text. :param relative_file_path: The relative path of the file to open. @@ -409,7 +426,7 @@ class SolidLanguageServer(ABC): "insert_text_at_position called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) uri = pathlib.Path(absolute_file_path).as_uri() @@ -439,13 +456,13 @@ class SolidLanguageServer(ABC): ], } ) - return multilspy_types.Position(line=new_l, character=new_c) + return ls_types.Position(line=new_l, character=new_c) def delete_text_between_positions( - self, - relative_file_path: str, - start: multilspy_types.Position, - end: multilspy_types.Position, + self, + relative_file_path: str, + start: ls_types.Position, + end: ls_types.Position, ) -> str: """ Delete text between the given start and end positions in the given file and return the deleted text. @@ -455,7 +472,7 @@ class SolidLanguageServer(ABC): "insert_text_at_position called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) uri = pathlib.Path(absolute_file_path).as_uri() @@ -465,7 +482,9 @@ class SolidLanguageServer(ABC): file_buffer = self.open_file_buffers[uri] file_buffer.version += 1 - new_contents, deleted_text = TextUtils.delete_text_between_positions(file_buffer.contents, start_line=start["line"], start_col=start["character"], end_line=end["line"], end_col=end["character"]) + new_contents, deleted_text = TextUtils.delete_text_between_positions( + file_buffer.contents, start_line=start["line"], start_col=start["character"], end_line=end["line"], end_col=end["character"] + ) file_buffer.contents = new_contents self.server.notify.did_change_text_document( { @@ -478,12 +497,10 @@ class SolidLanguageServer(ABC): ) return deleted_text - def _send_definition_request(self, definition_params: DefinitionParams) -> Union[Definition, List[LocationLink], None]: + def _send_definition_request(self, definition_params: DefinitionParams) -> Definition | list[LocationLink] | None: return self.server.send.definition(definition_params) - def request_definition( - self, relative_file_path: str, line: int, column: int - ) -> List[multilspy_types.Location]: + def request_definition(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]: """ Raise a [textDocument/definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) request to the Language Server for the symbol at the given line and column in the given file. Wait for the response and return the result. @@ -494,52 +511,52 @@ class SolidLanguageServer(ABC): :return List[multilspy_types.Location]: A list of locations where the symbol is defined """ - if not self.server_started: self.logger.log( "find_function_definition called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") with self.open_file(relative_file_path): # sending request to the language server and waiting for response - definition_params = cast(DefinitionParams, { - LSPConstants.TEXT_DOCUMENT: { - LSPConstants.URI: pathlib.Path( - str(PurePath(self.repository_root_path, relative_file_path)) - ).as_uri() + definition_params = cast( + DefinitionParams, + { + LSPConstants.TEXT_DOCUMENT: { + LSPConstants.URI: pathlib.Path(str(PurePath(self.repository_root_path, relative_file_path))).as_uri() + }, + LSPConstants.POSITION: { + LSPConstants.LINE: line, + LSPConstants.CHARACTER: column, + }, }, - LSPConstants.POSITION: { - LSPConstants.LINE: line, - LSPConstants.CHARACTER: column, - }, - }) + ) response = self._send_definition_request(definition_params) - ret: List[multilspy_types.Location] = [] + ret: list[ls_types.Location] = [] if isinstance(response, list): # response is either of type Location[] or LocationLink[] for item in response: assert isinstance(item, dict) if LSPConstants.URI in item and LSPConstants.RANGE in item: - new_item: multilspy_types.Location = {} + new_item: ls_types.Location = {} new_item.update(item) new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) - ret.append(multilspy_types.Location(new_item)) + ret.append(ls_types.Location(new_item)) elif ( - LSPConstants.ORIGIN_SELECTION_RANGE in item - and LSPConstants.TARGET_URI in item - and LSPConstants.TARGET_RANGE in item - and LSPConstants.TARGET_SELECTION_RANGE in item + LSPConstants.ORIGIN_SELECTION_RANGE in item + and LSPConstants.TARGET_URI in item + and LSPConstants.TARGET_RANGE in item + and LSPConstants.TARGET_SELECTION_RANGE in item ): - new_item: multilspy_types.Location = {} + new_item: ls_types.Location = {} new_item["uri"] = item[LSPConstants.TARGET_URI] new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) new_item["range"] = item[LSPConstants.TARGET_SELECTION_RANGE] - ret.append(multilspy_types.Location(**new_item)) + ret.append(ls_types.Location(**new_item)) else: assert False, f"Unexpected response from Language Server: {item}" elif isinstance(response, dict): @@ -547,11 +564,11 @@ class SolidLanguageServer(ABC): assert LSPConstants.URI in response assert LSPConstants.RANGE in response - new_item: multilspy_types.Location = {} + new_item: ls_types.Location = {} new_item.update(response) new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"]) new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path) - ret.append(multilspy_types.Location(**new_item)) + ret.append(ls_types.Location(**new_item)) 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 @@ -565,7 +582,7 @@ class SolidLanguageServer(ABC): return ret # Some LS cause problems with this, so the call is isolated from the rest to allow overriding in subclasses - def _send_references_request(self, relative_file_path: str, line: int, column: int) -> List[lsp_types.Location] | None: + def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None: return self.server.send.references( { "textDocument": {"uri": PathUtils.path_to_uri(os.path.join(self.repository_root_path, relative_file_path))}, @@ -574,9 +591,7 @@ class SolidLanguageServer(ABC): } ) - def request_references( - self, relative_file_path: str, line: int, column: int - ) -> List[multilspy_types.Location]: + def request_references(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]: """ Raise a [textDocument/references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) request to the Language Server to find references to the symbol at the given line and column in the given file. Wait for the response and return the result. @@ -588,21 +603,19 @@ 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, ) - raise MultilspyException("Language Server not started") - + raise LanguageServerException("Language Server not started") with self.open_file(relative_file_path): try: response = self._send_references_request(relative_file_path, line=line, column=column) except Exception as e: # Catch LSP internal error (-32603) and raise a more informative exception - if isinstance(e, Error) and getattr(e, 'code', None) == -32603: + if isinstance(e, Error) and getattr(e, "code", None) == -32603: raise RuntimeError( f"LSP internal error (-32603) when requesting references for {relative_file_path}:{line}:{column}. " "This often occurs when requesting references for a symbol not referenced in the expected way. " @@ -611,7 +624,7 @@ class SolidLanguageServer(ABC): if response is None: return [] - ret: List[multilspy_types.Location] = [] + ret: list[ls_types.Location] = [] assert isinstance(response, list), f"Unexpected response from Language Server (expected list, got {type(response)}): {response}" for item in response: assert isinstance(item, dict), f"Unexpected response from Language Server (expected dict, got {type(item)}): {item}" @@ -624,17 +637,17 @@ class SolidLanguageServer(ABC): self.logger.log(f"Ignoring reference in {rel_path} since it should be ignored", logging.DEBUG) continue - new_item: multilspy_types.Location = {} + new_item: ls_types.Location = {} new_item.update(item) new_item["absolutePath"] = str(abs_path) new_item["relativePath"] = str(rel_path) - ret.append(multilspy_types.Location(**new_item)) + ret.append(ls_types.Location(**new_item)) return ret def request_references_with_content( - self, relative_file_path: str, line: int, column: int, context_lines_before: int = 0, context_lines_after: int = 0 - ) -> List[MatchedConsecutiveLines]: + self, relative_file_path: str, line: int, column: int, context_lines_before: int = 0, context_lines_after: int = 0 + ) -> list[MatchedConsecutiveLines]: """ Like request_references, but returns the content of the lines containing the references, not just the locations. @@ -647,7 +660,10 @@ class SolidLanguageServer(ABC): :return: A list of MatchedConsecutiveLines objects, one for each reference. """ references = self.request_references(relative_file_path, line, column) - return [self.retrieve_content_around_line(ref["relativePath"], ref["range"]["start"]["line"], context_lines_before, context_lines_after) for ref in references] + return [ + self.retrieve_content_around_line(ref["relativePath"], ref["range"]["start"]["line"], context_lines_before, context_lines_after) + for ref in references + ] def retrieve_full_file_content(self, relative_file_path: str) -> str: """ @@ -656,7 +672,9 @@ class SolidLanguageServer(ABC): with self.open_file(relative_file_path) as file_data: return file_data.contents - def retrieve_content_around_line(self, relative_file_path: str, line: int, context_lines_before: int = 0, context_lines_after: int = 0) -> MatchedConsecutiveLines: + def retrieve_content_around_line( + self, relative_file_path: str, line: int, context_lines_before: int = 0, context_lines_after: int = 0 + ) -> MatchedConsecutiveLines: """ Retrieve the content of the given file around the given line. @@ -669,11 +687,17 @@ class SolidLanguageServer(ABC): """ with self.open_file(relative_file_path) as file_data: file_contents = file_data.contents - return MatchedConsecutiveLines.from_file_contents(file_contents, line=line, context_lines_before=context_lines_before, context_lines_after=context_lines_after, source_file_path=relative_file_path) + return MatchedConsecutiveLines.from_file_contents( + file_contents, + line=line, + context_lines_before=context_lines_before, + context_lines_after=context_lines_after, + source_file_path=relative_file_path, + ) def request_completions( - self, relative_file_path: str, line: int, column: int, allow_incomplete: bool = False - ) -> List[multilspy_types.CompletionItem]: + self, relative_file_path: str, line: int, column: int, allow_incomplete: bool = False + ) -> list[ls_types.CompletionItem]: """ Raise a [textDocument/completion](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion) request to the Language Server to find completions at the given line and column in the given file. Wait for the response and return the result. @@ -685,39 +709,35 @@ class SolidLanguageServer(ABC): :return List[multilspy_types.CompletionItem]: 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() - ] + open_file_buffer = self.open_file_buffers[pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri()] completion_params: LSPTypes.CompletionParams = { "position": {"line": line, "character": column}, "textDocument": {"uri": open_file_buffer.uri}, "context": {"triggerKind": LSPTypes.CompletionTriggerKind.Invoked}, } - response: Union[List[LSPTypes.CompletionItem], LSPTypes.CompletionList, None] = None + response: list[LSPTypes.CompletionItem] | LSPTypes.CompletionList | None = None num_retries = 0 while response is None or (response["isIncomplete"] and num_retries < 30): self.completions_available.wait() - response: Union[ - List[LSPTypes.CompletionItem], LSPTypes.CompletionList, None - ] = self.server.send.completion(completion_params) + response: list[LSPTypes.CompletionItem] | LSPTypes.CompletionList | None = self.server.send.completion(completion_params) if isinstance(response, list): response = {"items": response, "isIncomplete": False} num_retries += 1 # TODO: Understand how to appropriately handle `isIncomplete` - if response is None or (response["isIncomplete"] and not(allow_incomplete)): + if response is None or (response["isIncomplete"] and not (allow_incomplete)): return [] if "items" in response: response = response["items"] - response: List[LSPTypes.CompletionItem] = response + response = cast(list[LSPTypes.CompletionItem], response) # TODO: Handle the case when the completion is a keyword items = [item for item in response if item["kind"] != LSPTypes.CompletionItemKind.Keyword] - completions_list: List[multilspy_types.CompletionItem] = [] + completions_list: list[ls_types.CompletionItem] = [] for item in items: assert "insertText" in item or "textEdit" in item @@ -745,8 +765,7 @@ class SolidLanguageServer(ABC): item["textEdit"]["range"]["start"]["line"] == new_dot_lineno, item["textEdit"]["range"]["start"]["character"] == new_dot_colno, item["textEdit"]["range"]["start"]["line"] == item["textEdit"]["range"]["end"]["line"], - item["textEdit"]["range"]["start"]["character"] - == item["textEdit"]["range"]["end"]["character"], + item["textEdit"]["range"]["start"]["character"] == item["textEdit"]["range"]["end"]["character"], ) ) @@ -757,15 +776,14 @@ class SolidLanguageServer(ABC): else: assert False - completion_item = multilspy_types.CompletionItem(**completion_item) + completion_item = ls_types.CompletionItem(**completion_item) completions_list.append(completion_item) - return [ - json.loads(json_repr) - for json_repr in set([json.dumps(item, sort_keys=True) for item in completions_list]) - ] + return [json.loads(json_repr) for json_repr in set(json.dumps(item, sort_keys=True) for item in completions_list)] - def request_document_symbols(self, relative_file_path: str, include_body: bool = False) -> Tuple[List[multilspy_types.UnifiedSymbolInformation], List[multilspy_types.UnifiedSymbolInformation]]: + def request_document_symbols( + self, relative_file_path: str, include_body: bool = False + ) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]: """ Raise a [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol) request to the Language Server to find symbols in the given file. Wait for the response and return the result. @@ -797,36 +815,35 @@ class SolidLanguageServer(ABC): self.logger.log(f"Requesting document symbols for {relative_file_path} from the Language Server", logging.DEBUG) response = self.server.send.document_symbol( - { - "textDocument": { - "uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri() - } - } + {"textDocument": {"uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri()}} + ) + self.logger.log( + f"Received {len(response) if response is not None else None} document symbols for {relative_file_path} from the Language Server", + logging.DEBUG, ) - self.logger.log(f"Received {len(response) if response is not None else None} document symbols for {relative_file_path} from the Language Server", logging.DEBUG) def turn_item_into_symbol_with_children(item: GenericDocumentSymbol): - item = cast(multilspy_types.UnifiedSymbolInformation, item) + item = cast(ls_types.UnifiedSymbolInformation, item) absolute_path = os.path.join(self.repository_root_path, relative_file_path) # handle missing entries in location if "location" not in item: uri = pathlib.Path(absolute_path).as_uri() assert "range" in item - tree_location = multilspy_types.Location( + tree_location = ls_types.Location( uri=uri, - range=item['range'], + range=item["range"], absolutePath=absolute_path, relativePath=relative_file_path, ) - item['location'] = tree_location + item["location"] = tree_location location = item["location"] if "absolutePath" not in location: location["absolutePath"] = absolute_path if "relativePath" not in location: location["relativePath"] = relative_file_path if include_body: - item['body'] = self.retrieve_symbol_body(item) + item["body"] = self.retrieve_symbol_body(item) # handle missing selectionRange if "selectionRange" not in item: if "range" in item: @@ -838,9 +855,9 @@ class SolidLanguageServer(ABC): child["parent"] = item item[LSPConstants.CHILDREN] = children - flat_all_symbol_list: List[multilspy_types.UnifiedSymbolInformation] = [] + flat_all_symbol_list: list[ls_types.UnifiedSymbolInformation] = [] assert isinstance(response, list), f"Unexpected response from Language Server: {response}" - root_nodes: List[multilspy_types.UnifiedSymbolInformation] = [] + root_nodes: list[ls_types.UnifiedSymbolInformation] = [] for root_item in response: if "range" not in root_item and "location" not in root_item: if root_item["kind"] in [SymbolKind.File, SymbolKind.Module]: @@ -850,7 +867,7 @@ class SolidLanguageServer(ABC): # so we cast and rename the var after the mutating call to turn_item_into_symbol_with_children # which turned and item into a "symbol" turn_item_into_symbol_with_children(root_item) - root_symbol = cast(multilspy_types.UnifiedSymbolInformation, root_item) + root_symbol = cast(ls_types.UnifiedSymbolInformation, root_item) root_symbol["parent"] = None root_nodes.append(root_symbol) @@ -861,9 +878,9 @@ class SolidLanguageServer(ABC): if LSPConstants.CHILDREN in root_symbol: # TODO: l_tree should be a list of TreeRepr. Define the following function to return TreeRepr as well - def visit_tree_nodes_and_build_tree_repr(node: GenericDocumentSymbol) -> List[multilspy_types.UnifiedSymbolInformation]: - node = cast(multilspy_types.UnifiedSymbolInformation, node) - l: List[multilspy_types.UnifiedSymbolInformation] = [] + def visit_tree_nodes_and_build_tree_repr(node: GenericDocumentSymbol) -> list[ls_types.UnifiedSymbolInformation]: + node = cast(ls_types.UnifiedSymbolInformation, node) + l: list[ls_types.UnifiedSymbolInformation] = [] turn_item_into_symbol_with_children(node) assert LSPConstants.CHILDREN in node children = node[LSPConstants.CHILDREN] @@ -874,7 +891,7 @@ class SolidLanguageServer(ABC): flat_all_symbol_list.extend(visit_tree_nodes_and_build_tree_repr(root_symbol)) else: - flat_all_symbol_list.append(multilspy_types.UnifiedSymbolInformation(**root_symbol)) + flat_all_symbol_list.append(ls_types.UnifiedSymbolInformation(**root_symbol)) result = flat_all_symbol_list, root_nodes self.logger.log(f"Caching document symbols for {relative_file_path}", logging.DEBUG) @@ -883,9 +900,11 @@ class SolidLanguageServer(ABC): self._cache_has_changed = True return result - def request_full_symbol_tree(self, within_relative_path: str | None = None, include_body: bool = False) -> List[multilspy_types.UnifiedSymbolInformation]: + def request_full_symbol_tree( + self, within_relative_path: str | None = None, include_body: bool = False + ) -> list[ls_types.UnifiedSymbolInformation]: """ - Will go through all files in the project or within a relative path and build a tree of symbols. + Will go through all files in the project or within a relative path and build a tree of symbols. Note: this may be slow the first time it is called, especially if `within_relative_path` is not used to restrict the search. For each file, a symbol of kind File (2) will be created. For directories, a symbol of kind Package (4) will be created. @@ -902,21 +921,23 @@ class SolidLanguageServer(ABC): :return: A list of root symbols representing the top-level packages/modules in the project. """ - if within_relative_path is not None: within_abs_path = os.path.join(self.repository_root_path, within_relative_path) if not os.path.exists(within_abs_path): raise FileNotFoundError(f"File or directory not found: {within_abs_path}") if os.path.isfile(within_abs_path): if self.is_ignored_path(within_relative_path): - self.logger.log(f"You passed a file explicitly, but it is ignored. This is probably an error. File: {within_relative_path}", logging.ERROR) + self.logger.log( + f"You passed a file explicitly, but it is ignored. This is probably an error. File: {within_relative_path}", + logging.ERROR, + ) return [] else: _, root_nodes = self.request_document_symbols(within_relative_path, include_body=include_body) return root_nodes # Helper function to recursively process directories - def process_directory(rel_dir_path: str) -> List[multilspy_types.UnifiedSymbolInformation]: + def process_directory(rel_dir_path: str) -> list[ls_types.UnifiedSymbolInformation]: abs_dir_path = self.repository_root_path if rel_dir_path == "." else os.path.join(self.repository_root_path, rel_dir_path) abs_dir_path = os.path.realpath(abs_dir_path) @@ -931,16 +952,16 @@ class SolidLanguageServer(ABC): return [] # Create package symbol for directory - package_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore + package_symbol = ls_types.UnifiedSymbolInformation( # type: ignore name=os.path.basename(abs_dir_path), - kind=multilspy_types.SymbolKind.Package, - location=multilspy_types.Location( + kind=ls_types.SymbolKind.Package, + location=ls_types.Location( uri=str(pathlib.Path(abs_dir_path).as_uri()), range={"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}, absolutePath=str(abs_dir_path), relativePath=str(Path(abs_dir_path).resolve().relative_to(self.repository_root_path)), ), - children=[] + children=[], ) result.append(package_symbol) @@ -964,12 +985,12 @@ class SolidLanguageServer(ABC): file_rel_path = str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path)) with self.open_file(file_rel_path) as file_data: fileRange = self._get_range_from_file_content(file_data.contents) - file_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore + file_symbol = ls_types.UnifiedSymbolInformation( # type: ignore name=os.path.splitext(contained_dir_or_file_name)[0], - kind=multilspy_types.SymbolKind.File, + kind=ls_types.SymbolKind.File, range=fileRange, selectionRange=fileRange, - location=multilspy_types.Location( + location=ls_types.Location( uri=str(pathlib.Path(contained_dir_or_file_abs_path).as_uri()), range=fileRange, absolutePath=str(contained_dir_or_file_abs_path), @@ -985,7 +1006,7 @@ class SolidLanguageServer(ABC): package_symbol["children"].append(file_symbol) # TODO: Not sure if this is actually still needed given recent changes to relative path handling - def fix_relative_path(nodes: List[multilspy_types.UnifiedSymbolInformation]): + def fix_relative_path(nodes: list[ls_types.UnifiedSymbolInformation]): for node in nodes: if "location" in node and "relativePath" in node["location"]: path = Path(node["location"]["relativePath"]) @@ -1007,19 +1028,18 @@ class SolidLanguageServer(ABC): return process_directory(start_rel_path) @staticmethod - def _get_range_from_file_content(file_content: str) -> multilspy_types.Range: + def _get_range_from_file_content(file_content: str) -> ls_types.Range: """ Get the range for the given file. """ lines = file_content.split("\n") end_line = len(lines) end_column = len(lines[-1]) - return multilspy_types.Range( - start=multilspy_types.Position(line=0, character=0), - end=multilspy_types.Position(line=end_line, character=end_column) + return ls_types.Range( + start=ls_types.Position(line=0, character=0), end=ls_types.Position(line=end_line, character=end_column) ) - def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: + def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]]: """ An overview of the given directory. @@ -1028,22 +1048,24 @@ class SolidLanguageServer(ABC): """ symbol_tree = self.request_full_symbol_tree(relative_dir_path) # Initialize result dictionary - result: dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]] = defaultdict(list) + result: dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]] = defaultdict(list) # Helper function to process a symbol and its children - def process_symbol(symbol: multilspy_types.UnifiedSymbolInformation): - if symbol["kind"] == multilspy_types.SymbolKind.File: + def process_symbol(symbol: ls_types.UnifiedSymbolInformation): + if symbol["kind"] == ls_types.SymbolKind.File: # For file symbols, process their children (top-level symbols) for child in symbol["children"]: assert "location" in child assert "selectionRange" in child path = Path(child["location"]["absolutePath"]).resolve().relative_to(self.repository_root_path) - result[str(path)].append(( - child["name"], - child["kind"], - child["selectionRange"]["start"]["line"], - child["selectionRange"]["start"]["character"] - )) + result[str(path)].append( + ( + child["name"], + child["kind"], + child["selectionRange"]["start"]["line"], + child["selectionRange"]["start"]["character"], + ) + ) # For package/directory symbols, process their children for child in symbol["children"]: process_symbol(child) @@ -1053,7 +1075,7 @@ class SolidLanguageServer(ABC): process_symbol(root) return result - def request_document_overview(self, relative_file_path: str) -> list[tuple[str, multilspy_types.SymbolKind, int, int]]: + def request_document_overview(self, relative_file_path: str) -> list[tuple[str, ls_types.SymbolKind, int, int]]: """ An overview of the given file. Returns the list of tuples (name, kind, line, column) of all top-level symbols in the file. @@ -1063,18 +1085,13 @@ class SolidLanguageServer(ABC): for root in document_roots: try: result.append( - ( root["name"], - root["kind"], - root["selectionRange"]["start"]["line"], - root["selectionRange"]["start"]["character"],) + (root["name"], root["kind"], root["selectionRange"]["start"]["line"], root["selectionRange"]["start"]["character"]) ) except KeyError as e: - raise KeyError( - f"Could not process symbol of name {root.get('name', 'unknown')} in {relative_file_path=}" - ) from e + raise KeyError(f"Could not process symbol of name {root.get('name', 'unknown')} in {relative_file_path=}") from e return result - def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: + def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]]: """ An overview of all symbols in the given file or directory. @@ -1091,7 +1108,7 @@ class SolidLanguageServer(ABC): else: return self.request_dir_overview(within_relative_path) - def request_hover(self, relative_file_path: str, line: int, column: int) -> Union[multilspy_types.Hover, None]: + def request_hover(self, relative_file_path: str, line: int, column: int) -> ls_types.Hover | None: """ Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server to find the hover information at the given line and column in the given file. Wait for the response and return the result. @@ -1105,9 +1122,7 @@ class SolidLanguageServer(ABC): with self.open_file(relative_file_path): response = self.server.send.hover( { - "textDocument": { - "uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri() - }, + "textDocument": {"uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri()}, "position": { "line": line, "character": column, @@ -1120,11 +1135,13 @@ class SolidLanguageServer(ABC): assert isinstance(response, dict) - return multilspy_types.Hover(**response) + return ls_types.Hover(**response) # ----------------------------- FROM HERE ON MODIFICATIONS BY MISCHA -------------------- - def retrieve_symbol_body(self, symbol: multilspy_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation) -> str: + def retrieve_symbol_body( + self, symbol: ls_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation + ) -> str: """ Load the body of the given symbol. If the body is already contained in the symbol, just return it. """ @@ -1138,7 +1155,7 @@ class SolidLanguageServer(ABC): assert "relativePath" in symbol["location"] symbol_file = self.retrieve_full_file_content(symbol["location"]["relativePath"]) symbol_lines = symbol_file.split("\n") - symbol_body = "\n".join(symbol_lines[symbol_start_line:symbol_end_line+1]) + symbol_body = "\n".join(symbol_lines[symbol_start_line : symbol_end_line + 1]) # remove leading indentation symbol_start_column = symbol["location"]["range"]["start"]["character"] @@ -1152,7 +1169,7 @@ class SolidLanguageServer(ABC): "request_parsed_files called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") rel_file_paths = [] for root, dirs, files in os.walk(self.repository_root_path): dirs[:] = [d for d in dirs if not self.is_ignored_path(os.path.join(root, d))] @@ -1163,12 +1180,12 @@ class SolidLanguageServer(ABC): return rel_file_paths def search_files_for_pattern( - self, - pattern: re.Pattern | str, - context_lines_before: int = 0, - context_lines_after: int = 0, - paths_include_glob: str | None = None, - paths_exclude_glob: str | None = None, + self, + pattern: re.Pattern | str, + context_lines_before: int = 0, + context_lines_after: int = 0, + paths_include_glob: str | None = None, + paths_exclude_glob: str | None = None, ) -> list[MatchedConsecutiveLines]: """ Search for a pattern across all files analyzed by the Language Server. @@ -1191,19 +1208,19 @@ class SolidLanguageServer(ABC): context_lines_before=context_lines_before, context_lines_after=context_lines_after, paths_include_glob=paths_include_glob, - paths_exclude_glob=paths_exclude_glob + paths_exclude_glob=paths_exclude_glob, ) def request_referencing_symbols( - self, - relative_file_path: str, - line: int, - column: int, - include_imports: bool = True, - include_self: bool = False, - include_body: bool = False, - include_file_symbols: bool = False, - ) -> List[ReferenceInSymbol]: + self, + relative_file_path: str, + line: int, + column: int, + include_imports: bool = True, + include_self: bool = False, + include_body: bool = False, + include_file_symbols: bool = False, + ) -> list[ReferenceInSymbol]: """ Finds all symbols that reference the symbol at the given location. This is similar to request_references but filters to only include symbols @@ -1227,7 +1244,7 @@ class SolidLanguageServer(ABC): "request_referencing_symbols called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") # First, get all references to the symbol references = self.request_references(relative_file_path, line, column) @@ -1244,9 +1261,7 @@ class SolidLanguageServer(ABC): with self.open_file(ref_path) as file_data: # Get the containing symbol for this reference - containing_symbol = self.request_containing_symbol( - ref_path, ref_line, ref_col, include_body=include_body - ) + containing_symbol = self.request_containing_symbol(ref_path, ref_line, ref_col, include_body=include_body) if containing_symbol is None: # TODO: HORRIBLE HACK! I don't know how to do it better for now... # THIS IS BOUND TO BREAK IN MANY CASES! IT IS ALSO SPECIFIC TO PYTHON! @@ -1266,7 +1281,7 @@ class SolidLanguageServer(ABC): containing_symbol_name = ref_text.split(".")[0] all_symbols, _ = self.request_document_symbols(ref_path) for symbol in all_symbols: - if symbol["name"] == containing_symbol_name and symbol["kind"] == multilspy_types.SymbolKind.Variable: + if symbol["name"] == containing_symbol_name and symbol["kind"] == ls_types.SymbolKind.Variable: containing_symbol = copy(symbol) containing_symbol["location"] = ref containing_symbol["range"] = ref["range"] @@ -1276,10 +1291,10 @@ class SolidLanguageServer(ABC): 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 + logging.WARNING, ) fileRange = self._get_range_from_file_content(file_data.contents) - location = multilspy_types.Location( + location = ls_types.Location( uri=str(pathlib.Path(os.path.join(self.repository_root_path, ref_path)).as_uri()), range=fileRange, absolutePath=str(os.path.join(self.repository_root_path, ref_path)), @@ -1292,8 +1307,8 @@ class SolidLanguageServer(ABC): else: body = "" - containing_symbol = multilspy_types.UnifiedSymbolInformation( - kind=multilspy_types.SymbolKind.File, + containing_symbol = ls_types.UnifiedSymbolInformation( + kind=ls_types.SymbolKind.File, range=fileRange, selectionRange=fileRange, location=location, @@ -1301,7 +1316,7 @@ class SolidLanguageServer(ABC): children=[], body=body, ) - if containing_symbol is None or not include_file_symbols and containing_symbol["kind"] == multilspy_types.SymbolKind.File: + if containing_symbol is None or (not include_file_symbols and containing_symbol["kind"] == ls_types.SymbolKind.File): continue assert "location" in containing_symbol @@ -1309,31 +1324,31 @@ class SolidLanguageServer(ABC): # Checking for self-reference if ( - containing_symbol["location"]["relativePath"] == relative_file_path - and containing_symbol["selectionRange"]["start"]["line"] == ref_line - and containing_symbol["selectionRange"]["start"]["character"] == ref_col + containing_symbol["location"]["relativePath"] == relative_file_path + and containing_symbol["selectionRange"]["start"]["line"] == ref_line + and containing_symbol["selectionRange"]["start"]["character"] == ref_col ): incoming_symbol = containing_symbol if include_self: result.append(ReferenceInSymbol(symbol=containing_symbol, line=ref_line, character=ref_col)) continue - else: - self.logger.log(f"Found self-reference for {incoming_symbol['name']}, skipping it since {include_self=}", logging.DEBUG) - continue + self.logger.log(f"Found self-reference for {incoming_symbol['name']}, skipping it since {include_self=}", logging.DEBUG) + continue # checking whether reference is an import # This is neither really safe nor elegant, but if we don't do it, # there is no way to distinguish between definitions and imports as import is not a symbol-type # and we get the type referenced symbol resulting from imports... - if (not include_imports \ - and incoming_symbol is not None \ - and containing_symbol["name"] == incoming_symbol["name"] \ - and containing_symbol["kind"] == incoming_symbol["kind"] \ - ): + if ( + not include_imports + and incoming_symbol is not None + and containing_symbol["name"] == incoming_symbol["name"] + and containing_symbol["kind"] == incoming_symbol["kind"] + ): self.logger.log( f"Found import of referenced symbol {incoming_symbol['name']}" f"in {containing_symbol['location']['relativePath']}, skipping", - logging.DEBUG + logging.DEBUG, ) continue @@ -1342,13 +1357,13 @@ class SolidLanguageServer(ABC): return result def request_containing_symbol( - self, - relative_file_path: str, - line: int, - column: Optional[int] = None, - strict: bool = False, - include_body: bool = False, - ) -> multilspy_types.UnifiedSymbolInformation | None: + self, + relative_file_path: str, + line: int, + column: int | None = None, + strict: bool = False, + include_body: bool = False, + ) -> ls_types.UnifiedSymbolInformation | None: """ Finds the first symbol containing the position for the given file. For Python, container symbols are considered to be those with kinds corresponding to @@ -1377,9 +1392,7 @@ class SolidLanguageServer(ABC): """ # checking if the line is empty, unfortunately ugly and duplicating code, but I don't want to refactor with self.open_file(relative_file_path): - absolute_file_path = str( - PurePath(self.repository_root_path, relative_file_path) - ) + absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) content = FileUtils.read_file(self.logger, absolute_file_path) if content.split("\n")[line].strip() == "": self.logger.log( @@ -1396,7 +1409,7 @@ class SolidLanguageServer(ABC): for symbol in symbols: if "location" not in symbol: range = symbol["range"] - location = multilspy_types.Location( + location = ls_types.Location( uri=f"file:/{absolute_file_path}", range=range, absolutePath=absolute_file_path, @@ -1411,13 +1424,9 @@ class SolidLanguageServer(ABC): location["uri"] = Path(absolute_file_path).as_uri() # Allowed container kinds, currently only for Python - container_symbol_kinds = { - multilspy_types.SymbolKind.Method, - multilspy_types.SymbolKind.Function, - multilspy_types.SymbolKind.Class - } + container_symbol_kinds = {ls_types.SymbolKind.Method, ls_types.SymbolKind.Function, ls_types.SymbolKind.Class} - def is_position_in_range(line: int, range_d: multilspy_types.Range) -> bool: + def is_position_in_range(line: int, range_d: ls_types.Range) -> bool: start = range_d["start"] end = range_d["end"] @@ -1434,11 +1443,11 @@ class SolidLanguageServer(ABC): # Only consider containers that are not one-liners (otherwise we may get imports) candidate_containers = [ - s for s in symbols if s["kind"] in container_symbol_kinds and s["location"]["range"]["start"]["line"] != s["location"]["range"]["end"]["line"] - ] - var_containers = [ - s for s in symbols if s["kind"] == multilspy_types.SymbolKind.Variable + s + for s in symbols + if s["kind"] in container_symbol_kinds and s["location"]["range"]["start"]["line"] != s["location"]["range"]["end"]["line"] ] + var_containers = [s for s in symbols if s["kind"] == ls_types.SymbolKind.Variable] candidate_containers.extend(var_containers) if not candidate_containers: @@ -1461,7 +1470,9 @@ class SolidLanguageServer(ABC): else: return None - def request_container_of_symbol(self, symbol: multilspy_types.UnifiedSymbolInformation, include_body: bool = False) -> multilspy_types.UnifiedSymbolInformation | None: + def request_container_of_symbol( + self, symbol: ls_types.UnifiedSymbolInformation, include_body: bool = False + ) -> ls_types.UnifiedSymbolInformation | None: """ Finds the container of the given symbol if there is one. If the parent attribute is present, the parent is returned without further searching. @@ -1482,12 +1493,12 @@ class SolidLanguageServer(ABC): ) def request_defining_symbol( - self, - relative_file_path: str, - line: int, - column: int, - include_body: bool = False, - ) -> Optional[multilspy_types.UnifiedSymbolInformation]: + self, + relative_file_path: str, + line: int, + column: int, + include_body: bool = False, + ) -> ls_types.UnifiedSymbolInformation | None: """ Finds the symbol that defines the symbol at the given location. @@ -1505,7 +1516,7 @@ class SolidLanguageServer(ABC): "request_defining_symbol called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") # Get the definition location(s) definitions = self.request_definition(relative_file_path, line, column) @@ -1519,9 +1530,7 @@ class SolidLanguageServer(ABC): def_col = definition["range"]["start"]["character"] # Find the symbol at or containing this location - defining_symbol = self.request_containing_symbol( - def_path, def_line, def_col, strict=False, include_body=include_body - ) + defining_symbol = self.request_containing_symbol(def_path, def_line, def_col, strict=False, include_body=include_body) return defining_symbol @@ -1587,7 +1596,7 @@ class SolidLanguageServer(ABC): logging.ERROR, ) - def request_workspace_symbol(self, query: str) -> Union[List[multilspy_types.UnifiedSymbolInformation], None]: + def request_workspace_symbol(self, query: str) -> list[ls_types.UnifiedSymbolInformation] | None: """ Raise a [workspace/symbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) request to the Language Server to find symbols across the whole workspace. Wait for the response and return the result. @@ -1602,7 +1611,7 @@ class SolidLanguageServer(ABC): assert isinstance(response, list) - ret: List[multilspy_types.UnifiedSymbolInformation] = [] + ret: list[ls_types.UnifiedSymbolInformation] = [] for item in response: assert isinstance(item, dict) @@ -1610,7 +1619,7 @@ class SolidLanguageServer(ABC): assert LSPConstants.KIND in item assert LSPConstants.LOCATION in item - ret.append(multilspy_types.UnifiedSymbolInformation(**item)) + ret.append(ls_types.UnifiedSymbolInformation(**item)) return ret @@ -1620,7 +1629,10 @@ 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) + self.logger.log( + f"Starting language server with language {self.language_server.language} for {self.language_server.repository_root_path}", + logging.INFO, + ) self._server_context = self._start_server_process() return self diff --git a/src/multilspy/multilspy_config.py b/src/solidlsp/ls_config.py similarity index 93% rename from src/multilspy/multilspy_config.py rename to src/solidlsp/ls_config.py index c1d77ae0..27deaf15 100644 --- a/src/multilspy/multilspy_config.py +++ b/src/solidlsp/ls_config.py @@ -1,10 +1,10 @@ """ Configuration parameters for Multilspy. """ + import fnmatch -from enum import Enum -from typing import List from dataclasses import dataclass, field +from enum import Enum class FilenameMatcher: @@ -73,10 +73,11 @@ class Language(str, Enum): @dataclass -class MultilspyConfig: +class LanguageServerConfig: """ Configuration parameters """ + code_language: Language trace_lsp_communication: bool = False start_independent_lsp_process: bool = True @@ -89,7 +90,5 @@ class MultilspyConfig: 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 - }) + + return cls(**{k: v for k, v in env.items() if k in inspect.signature(cls).parameters}) diff --git a/src/multilspy/multilspy_exceptions.py b/src/solidlsp/ls_exceptions.py similarity index 77% rename from src/multilspy/multilspy_exceptions.py rename to src/solidlsp/ls_exceptions.py index d645d130..0484da6b 100644 --- a/src/multilspy/multilspy_exceptions.py +++ b/src/solidlsp/ls_exceptions.py @@ -2,7 +2,8 @@ This module contains the exceptions raised by the Multilspy framework. """ -class MultilspyException(Exception): + +class LanguageServerException(Exception): """ Exceptions raised by the Multilspy framework. """ @@ -11,4 +12,4 @@ class MultilspyException(Exception): """ Initializes the exception with the given message. """ - super().__init__(message) \ No newline at end of file + super().__init__(message) diff --git a/src/solidlsp/ls_handler.py b/src/solidlsp/ls_handler.py index b34571f9..6de9ef7e 100644 --- a/src/solidlsp/ls_handler.py +++ b/src/solidlsp/ls_handler.py @@ -5,18 +5,31 @@ import os import subprocess import threading import time +from collections.abc import Callable from dataclasses import dataclass from queue import Queue -from typing import Any, Callable, Dict, Optional +from typing import Any import psutil -from multilspy.lsp_protocol_handler.lsp_requests import LspNotification -from multilspy.lsp_protocol_handler.lsp_types import ErrorCodes -from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo, StringDict, content_length, ENCODING, Error, MessageType, \ - make_notification, PayloadLike, make_response, make_error_response, make_request, create_message -from multilspy.multilspy_exceptions import MultilspyException -from solidlsp.lsp_request import SolidLspRequest +from solidlsp.lsp_protocol_handler.lsp_requests import LspNotification +from solidlsp.lsp_protocol_handler.lsp_types import ErrorCodes +from solidlsp.lsp_protocol_handler.server import ( + ENCODING, + Error, + MessageType, + PayloadLike, + ProcessLaunchInfo, + StringDict, + content_length, + create_message, + make_error_response, + make_notification, + make_request, + make_response, +) +from solidlsp.ls_request import LanguageServerRequest +from solidlsp.ls_exceptions import LanguageServerException log = logging.getLogger(__name__) @@ -25,8 +38,8 @@ class Request: @dataclass class Result: - payload: Optional[PayloadLike] = None - error: Optional[Error] = None + payload: PayloadLike | None = None + error: Error | None = None def is_error(self) -> bool: return self.error is not None @@ -81,13 +94,14 @@ class SolidLanguageServerHandler: language server process in an independent process group. Default is `True`. Setting it to `False` means that the language server process will be in the same process group as the the current process, and any SIGINT and SIGTERM signals will be sent to both processes. + """ def __init__( - self, - process_launch_info: ProcessLaunchInfo, - logger: Optional[Callable[[str, str, StringDict | str], None]] = None, - start_independent_lsp_process=True, + self, + process_launch_info: ProcessLaunchInfo, + logger: Callable[[str, str, StringDict | str], None] | None = None, + start_independent_lsp_process=True, ) -> None: """ Params: @@ -95,7 +109,7 @@ class SolidLanguageServerHandler: logger: An optional function that takes two strings (source and destination) and a payload dictionary, and logs the communication between the client and the server. """ - self.send = SolidLspRequest(self.send_request) + self.send = LanguageServerRequest(self.send_request) self.notify = LspNotification(self.send_notification) self.process_launch_info = process_launch_info @@ -103,7 +117,7 @@ class SolidLanguageServerHandler: self._received_shutdown = False self.request_id = 1 - self._response_handlers: Dict[Any, Request] = {} + self._response_handlers: dict[Any, Request] = {} self.on_request_handlers = {} self.on_notification_handlers = {} self.logger = logger @@ -141,7 +155,7 @@ class SolidLanguageServerHandler: env=child_proc_env, cwd=self.process_launch_info.cwd, start_new_session=self.start_independent_lsp_process, - shell=True + shell=True, ) # Check if process terminated immediately @@ -149,7 +163,7 @@ class SolidLanguageServerHandler: log.error("Language server has already terminated/could not be started") # Process has already terminated stderr_data = self.process.stderr.read() - error_message = stderr_data.decode('utf-8', errors='replace') + error_message = stderr_data.decode("utf-8", errors="replace") raise RuntimeError(f"Process terminated immediately with code {self.process.returncode}. Error: {error_message}") # start threads to read stdout and stderr of the process @@ -284,7 +298,7 @@ class SolidLanguageServerHandler: # Process has terminated, check if we can still read pass - data = b'' + data = b"" while len(data) < num_bytes: chunk = stream.read(num_bytes - len(data)) if not chunk: @@ -332,7 +346,7 @@ class SolidLanguageServerHandler: line = self.process.stderr.readline() if not line: continue - self._log("LSP stderr: " + line.decode(ENCODING, errors='replace')) + self._log("LSP stderr: " + line.decode(ENCODING, errors="replace")) except (BrokenPipeError, ConnectionResetError): pass @@ -342,7 +356,7 @@ class SolidLanguageServerHandler: """ try: self._receive_payload(json.loads(body)) - except IOError as ex: + except OSError as ex: self._log(f"malformed {ENCODING}: {ex}") except UnicodeDecodeError as ex: self._log(f"malformed {ENCODING}: {ex}") @@ -368,7 +382,7 @@ class SolidLanguageServerHandler: except Exception as err: self._log(f"Error handling server payload: {err}") - def send_notification(self, method: str, params: Optional[dict] = None) -> None: + def send_notification(self, method: str, params: dict | None = None) -> None: """ Send notification pertaining to the given method to the server with the given parameters """ @@ -387,7 +401,7 @@ class SolidLanguageServerHandler: # Use lock to prevent race conditions on tasks and task_counter self._send_payload(make_error_response(request_id, err)) - def send_request(self, method: str, params: Optional[dict] = None) -> PayloadLike: + def send_request(self, method: str, params: dict | None = None) -> PayloadLike: """ Send request to the server, register the request id, and wait for the response """ @@ -406,10 +420,12 @@ class SolidLanguageServerHandler: self._log(f"Waiting for response to request {method} with params:\n{params}") result = request.get_result() - self._log(f"Processing result") + self._log("Processing result") if result.is_error(): - raise MultilspyException(f"Could not process request {method} with params:\n{params}.\n Language server error: {result.error}") from result.error - + raise LanguageServerException( + f"Could not process request {method} with params:\n{params}.\n Language server error: {result.error}" + ) from result.error + self._log(f"Returning non-error result, which is:\n{result.payload}") return result.payload @@ -472,7 +488,7 @@ class SolidLanguageServerHandler: request_id, Error( ErrorCodes.MethodNotFound, - "method '{}' not handled on client.".format(method), + f"method '{method}' not handled on client.", ), ) return diff --git a/src/multilspy/multilspy_logger.py b/src/solidlsp/ls_logger.py similarity index 98% rename from src/multilspy/multilspy_logger.py rename to src/solidlsp/ls_logger.py index c6135a55..6bdf12cb 100644 --- a/src/multilspy/multilspy_logger.py +++ b/src/solidlsp/ls_logger.py @@ -1,9 +1,11 @@ """ Multilspy logger module. """ + import inspect import logging from datetime import datetime + from pydantic import BaseModel @@ -20,7 +22,7 @@ class LogLine(BaseModel): message: str -class MultilspyLogger: +class LanguageServerLogger: """ Logger class """ @@ -34,7 +36,6 @@ class MultilspyLogger: """ 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", " ") diff --git a/src/solidlsp/lsp_request.py b/src/solidlsp/ls_request.py similarity index 62% rename from src/solidlsp/lsp_request.py rename to src/solidlsp/ls_request.py index 984407b5..209a3f04 100644 --- a/src/solidlsp/lsp_request.py +++ b/src/solidlsp/ls_request.py @@ -1,263 +1,236 @@ -from typing import List, Union -from multilspy.lsp_protocol_handler import lsp_types +from typing import Union + +from solidlsp.lsp_protocol_handler import lsp_types -class SolidLspRequest: +class LanguageServerRequest: def __init__(self, send_request): self.send_request = send_request - def implementation( - self, params: lsp_types.ImplementationParams - ) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]: + def implementation(self, params: lsp_types.ImplementationParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]: """A request to resolve the implementation locations of a symbol at a given text document position. The request's parameter is of type [TextDocumentPositionParams] (#TextDocumentPositionParams) the response is of type {@link Definition} or a - Thenable that resolves to such.""" + Thenable that resolves to such. + """ return self.send_request("textDocument/implementation", params) def type_definition( - self, params: lsp_types.TypeDefinitionParams - ) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]: + self, params: lsp_types.TypeDefinitionParams + ) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]: """A request to resolve the type definition locations of a symbol at a given text document position. The request's parameter is of type [TextDocumentPositionParams] (#TextDocumentPositionParams) the response is of type {@link Definition} or a - Thenable that resolves to such.""" + Thenable that resolves to such. + """ return self.send_request("textDocument/typeDefinition", params) - def document_color( - self, params: lsp_types.DocumentColorParams - ) -> List["lsp_types.ColorInformation"]: + def document_color(self, params: lsp_types.DocumentColorParams) -> list["lsp_types.ColorInformation"]: """A request to list all color symbols found in a given text document. The request's parameter is of type {@link DocumentColorParams} the response is of type {@link ColorInformation ColorInformation[]} or a Thenable - that resolves to such.""" + that resolves to such. + """ return self.send_request("textDocument/documentColor", params) - def color_presentation( - self, params: lsp_types.ColorPresentationParams - ) -> List["lsp_types.ColorPresentation"]: + def color_presentation(self, params: lsp_types.ColorPresentationParams) -> list["lsp_types.ColorPresentation"]: """A request to list all presentation for a color. The request's parameter is of type {@link ColorPresentationParams} the response is of type {@link ColorInformation ColorInformation[]} or a Thenable - that resolves to such.""" + that resolves to such. + """ return self.send_request("textDocument/colorPresentation", params) - def folding_range( - self, params: lsp_types.FoldingRangeParams - ) -> Union[List["lsp_types.FoldingRange"], None]: + def folding_range(self, params: lsp_types.FoldingRangeParams) -> list["lsp_types.FoldingRange"] | None: """A request to provide folding ranges in a document. The request's parameter is of type {@link FoldingRangeParams}, the response is of type {@link FoldingRangeList} or a Thenable - that resolves to such.""" + that resolves to such. + """ return self.send_request("textDocument/foldingRange", params) - def declaration( - self, params: lsp_types.DeclarationParams - ) -> Union["lsp_types.Declaration", List["lsp_types.LocationLink"], None]: + def declaration(self, params: lsp_types.DeclarationParams) -> Union["lsp_types.Declaration", list["lsp_types.LocationLink"], None]: """A request to resolve the type definition locations of a symbol at a given text document position. The request's parameter is of type [TextDocumentPositionParams] (#TextDocumentPositionParams) the response is of type {@link Declaration} or a typed array of {@link DeclarationLink} or a Thenable that resolves - to such.""" + to such. + """ return self.send_request("textDocument/declaration", params) - def selection_range( - self, params: lsp_types.SelectionRangeParams - ) -> Union[List["lsp_types.SelectionRange"], None]: + def selection_range(self, params: lsp_types.SelectionRangeParams) -> list["lsp_types.SelectionRange"] | None: """A request to provide selection ranges in a document. The request's parameter is of type {@link SelectionRangeParams}, the response is of type {@link SelectionRange SelectionRange[]} or a Thenable - that resolves to such.""" + that resolves to such. + """ return self.send_request("textDocument/selectionRange", params) - def prepare_call_hierarchy( - self, params: lsp_types.CallHierarchyPrepareParams - ) -> Union[List["lsp_types.CallHierarchyItem"], None]: + def prepare_call_hierarchy(self, params: lsp_types.CallHierarchyPrepareParams) -> list["lsp_types.CallHierarchyItem"] | None: """A request to result a `CallHierarchyItem` in a document at a given position. Can be used as an input to an incoming or outgoing call hierarchy. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_request("textDocument/prepareCallHierarchy", params) - def incoming_calls( - self, params: lsp_types.CallHierarchyIncomingCallsParams - ) -> Union[List["lsp_types.CallHierarchyIncomingCall"], None]: + def incoming_calls(self, params: lsp_types.CallHierarchyIncomingCallsParams) -> list["lsp_types.CallHierarchyIncomingCall"] | None: """A request to resolve the incoming calls for a given `CallHierarchyItem`. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_request("callHierarchy/incomingCalls", params) - def outgoing_calls( - self, params: lsp_types.CallHierarchyOutgoingCallsParams - ) -> Union[List["lsp_types.CallHierarchyOutgoingCall"], None]: + def outgoing_calls(self, params: lsp_types.CallHierarchyOutgoingCallsParams) -> list["lsp_types.CallHierarchyOutgoingCall"] | None: """A request to resolve the outgoing calls for a given `CallHierarchyItem`. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_request("callHierarchy/outgoingCalls", params) - def semantic_tokens_full( - self, params: lsp_types.SemanticTokensParams - ) -> Union["lsp_types.SemanticTokens", None]: + def semantic_tokens_full(self, params: lsp_types.SemanticTokensParams) -> Union["lsp_types.SemanticTokens", None]: """@since 3.16.0""" return self.send_request("textDocument/semanticTokens/full", params) def semantic_tokens_delta( - self, params: lsp_types.SemanticTokensDeltaParams + self, params: lsp_types.SemanticTokensDeltaParams ) -> Union["lsp_types.SemanticTokens", "lsp_types.SemanticTokensDelta", None]: """@since 3.16.0""" return self.send_request("textDocument/semanticTokens/full/delta", params) - def semantic_tokens_range( - self, params: lsp_types.SemanticTokensRangeParams - ) -> Union["lsp_types.SemanticTokens", None]: + def semantic_tokens_range(self, params: lsp_types.SemanticTokensRangeParams) -> Union["lsp_types.SemanticTokens", None]: """@since 3.16.0""" return self.send_request("textDocument/semanticTokens/range", params) - def linked_editing_range( - self, params: lsp_types.LinkedEditingRangeParams - ) -> Union["lsp_types.LinkedEditingRanges", None]: + def linked_editing_range(self, params: lsp_types.LinkedEditingRangeParams) -> Union["lsp_types.LinkedEditingRanges", None]: """A request to provide ranges that can be edited together. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_request("textDocument/linkedEditingRange", params) - def will_create_files( - self, params: lsp_types.CreateFilesParams - ) -> Union["lsp_types.WorkspaceEdit", None]: + def will_create_files(self, params: lsp_types.CreateFilesParams) -> Union["lsp_types.WorkspaceEdit", None]: """The will create files request is sent from the client to the server before files are actually created as long as the creation is triggered from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_request("workspace/willCreateFiles", params) - def will_rename_files( - self, params: lsp_types.RenameFilesParams - ) -> Union["lsp_types.WorkspaceEdit", None]: + def will_rename_files(self, params: lsp_types.RenameFilesParams) -> Union["lsp_types.WorkspaceEdit", None]: """The will rename files request is sent from the client to the server before files are actually renamed as long as the rename is triggered from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_request("workspace/willRenameFiles", params) - def will_delete_files( - self, params: lsp_types.DeleteFilesParams - ) -> Union["lsp_types.WorkspaceEdit", None]: + def will_delete_files(self, params: lsp_types.DeleteFilesParams) -> Union["lsp_types.WorkspaceEdit", None]: """The did delete files notification is sent from the client to the server when files were deleted from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_request("workspace/willDeleteFiles", params) - def moniker( - self, params: lsp_types.MonikerParams - ) -> Union[List["lsp_types.Moniker"], None]: + def moniker(self, params: lsp_types.MonikerParams) -> list["lsp_types.Moniker"] | None: """A request to get the moniker of a symbol at a given text document position. The request parameter is of type {@link TextDocumentPositionParams}. - The response is of type {@link Moniker Moniker[]} or `null`.""" + The response is of type {@link Moniker Moniker[]} or `null`. + """ return self.send_request("textDocument/moniker", params) - def prepare_type_hierarchy( - self, params: lsp_types.TypeHierarchyPrepareParams - ) -> Union[List["lsp_types.TypeHierarchyItem"], None]: + def prepare_type_hierarchy(self, params: lsp_types.TypeHierarchyPrepareParams) -> list["lsp_types.TypeHierarchyItem"] | None: """A request to result a `TypeHierarchyItem` in a document at a given position. Can be used as an input to a subtypes or supertypes type hierarchy. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("textDocument/prepareTypeHierarchy", params) - def type_hierarchy_supertypes( - self, params: lsp_types.TypeHierarchySupertypesParams - ) -> Union[List["lsp_types.TypeHierarchyItem"], None]: + def type_hierarchy_supertypes(self, params: lsp_types.TypeHierarchySupertypesParams) -> list["lsp_types.TypeHierarchyItem"] | None: """A request to resolve the supertypes for a given `TypeHierarchyItem`. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("typeHierarchy/supertypes", params) - def type_hierarchy_subtypes( - self, params: lsp_types.TypeHierarchySubtypesParams - ) -> Union[List["lsp_types.TypeHierarchyItem"], None]: + def type_hierarchy_subtypes(self, params: lsp_types.TypeHierarchySubtypesParams) -> list["lsp_types.TypeHierarchyItem"] | None: """A request to resolve the subtypes for a given `TypeHierarchyItem`. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("typeHierarchy/subtypes", params) - def inline_value( - self, params: lsp_types.InlineValueParams - ) -> Union[List["lsp_types.InlineValue"], None]: + def inline_value(self, params: lsp_types.InlineValueParams) -> list["lsp_types.InlineValue"] | None: """A request to provide inline values in a document. The request's parameter is of type {@link InlineValueParams}, the response is of type {@link InlineValue InlineValue[]} or a Thenable that resolves to such. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("textDocument/inlineValue", params) - def inlay_hint( - self, params: lsp_types.InlayHintParams - ) -> Union[List["lsp_types.InlayHint"], None]: + def inlay_hint(self, params: lsp_types.InlayHintParams) -> list["lsp_types.InlayHint"] | None: """A request to provide inlay hints in a document. The request's parameter is of type {@link InlayHintsParams}, the response is of type {@link InlayHint InlayHint[]} or a Thenable that resolves to such. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("textDocument/inlayHint", params) - def resolve_inlay_hint( - self, params: lsp_types.InlayHint - ) -> "lsp_types.InlayHint": + def resolve_inlay_hint(self, params: lsp_types.InlayHint) -> "lsp_types.InlayHint": """A request to resolve additional properties for an inlay hint. The request's parameter is of type {@link InlayHint}, the response is of type {@link InlayHint} or a Thenable that resolves to such. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("inlayHint/resolve", params) - def text_document_diagnostic( - self, params: lsp_types.DocumentDiagnosticParams - ) -> "lsp_types.DocumentDiagnosticReport": + def text_document_diagnostic(self, params: lsp_types.DocumentDiagnosticParams) -> "lsp_types.DocumentDiagnosticReport": """The document diagnostic request definition. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("textDocument/diagnostic", params) - def workspace_diagnostic( - self, params: lsp_types.WorkspaceDiagnosticParams - ) -> "lsp_types.WorkspaceDiagnosticReport": + def workspace_diagnostic(self, params: lsp_types.WorkspaceDiagnosticParams) -> "lsp_types.WorkspaceDiagnosticReport": """The workspace diagnostic request definition. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("workspace/diagnostic", params) - def initialize( - self, params: lsp_types.InitializeParams - ) -> "lsp_types.InitializeResult": + def initialize(self, params: lsp_types.InitializeParams) -> "lsp_types.InitializeResult": """The initialize request is sent from the client to the server. It is sent once as the request after starting up the server. The requests parameter is of type {@link InitializeParams} the response if of type {@link InitializeResult} of a Thenable that - resolves to such.""" + resolves to such. + """ return self.send_request("initialize", params) def shutdown(self) -> None: """A shutdown request is sent from the client to the server. It is sent once when the client decides to shutdown the server. The only notification that is sent after a shutdown request - is the exit event.""" + is the exit event. + """ return self.send_request("shutdown") - def will_save_wait_until( - self, params: lsp_types.WillSaveTextDocumentParams - ) -> Union[List["lsp_types.TextEdit"], None]: + def will_save_wait_until(self, params: lsp_types.WillSaveTextDocumentParams) -> list["lsp_types.TextEdit"] | None: """A document will save request is sent from the client to the server before the document is actually saved. The request can return an array of TextEdits which will be applied to the text document before it is saved. Please note that clients might drop results if computing the text edits took too long or if a server constantly fails on this request. This is done to keep the save fast and - reliable.""" + reliable. + """ return self.send_request("textDocument/willSaveWaitUntil", params) - def completion( - self, params: lsp_types.CompletionParams - ) -> Union[List["lsp_types.CompletionItem"], "lsp_types.CompletionList", None]: + def completion(self, params: lsp_types.CompletionParams) -> Union[list["lsp_types.CompletionItem"], "lsp_types.CompletionList", None]: """Request to request completion at a given text document position. The request's parameter is of type {@link TextDocumentPosition} the response is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList} @@ -270,85 +243,72 @@ class SolidLspRequest: """ return self.send_request("textDocument/completion", params) - def resolve_completion_item( - self, params: lsp_types.CompletionItem - ) -> "lsp_types.CompletionItem": + def resolve_completion_item(self, params: lsp_types.CompletionItem) -> "lsp_types.CompletionItem": """Request to resolve additional information for a given completion item.The request's parameter is of type {@link CompletionItem} the response - is of type {@link CompletionItem} or a Thenable that resolves to such.""" + is of type {@link CompletionItem} or a Thenable that resolves to such. + """ return self.send_request("completionItem/resolve", params) - def hover( - self, params: lsp_types.HoverParams - ) -> Union["lsp_types.Hover", None]: + def hover(self, params: lsp_types.HoverParams) -> Union["lsp_types.Hover", None]: """Request to request hover information at a given text document position. The request's parameter is of type {@link TextDocumentPosition} the response is of - type {@link Hover} or a Thenable that resolves to such.""" + type {@link Hover} or a Thenable that resolves to such. + """ return self.send_request("textDocument/hover", params) - def signature_help( - self, params: lsp_types.SignatureHelpParams - ) -> Union["lsp_types.SignatureHelp", None]: + def signature_help(self, params: lsp_types.SignatureHelpParams) -> Union["lsp_types.SignatureHelp", None]: return self.send_request("textDocument/signatureHelp", params) - def definition( - self, params: lsp_types.DefinitionParams - ) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]: + def definition(self, params: lsp_types.DefinitionParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]: """A request to resolve the definition location of a symbol at a given text document position. The request's parameter is of type [TextDocumentPosition] (#TextDocumentPosition) the response is of either type {@link Definition} or a typed array of {@link DefinitionLink} or a Thenable that resolves - to such.""" + to such. + """ return self.send_request("textDocument/definition", params) - def references( - self, params: lsp_types.ReferenceParams - ) -> Union[List["lsp_types.Location"], None]: + def references(self, params: lsp_types.ReferenceParams) -> list["lsp_types.Location"] | None: """A request to resolve project-wide references for the symbol denoted by the given text document position. The request's parameter is of type {@link ReferenceParams} the response is of type - {@link Location Location[]} or a Thenable that resolves to such.""" + {@link Location Location[]} or a Thenable that resolves to such. + """ return self.send_request("textDocument/references", params) - def document_highlight( - self, params: lsp_types.DocumentHighlightParams - ) -> Union[List["lsp_types.DocumentHighlight"], None]: + def document_highlight(self, params: lsp_types.DocumentHighlightParams) -> list["lsp_types.DocumentHighlight"] | None: """Request to resolve a {@link DocumentHighlight} for a given text document position. The request's parameter is of type [TextDocumentPosition] (#TextDocumentPosition) the request response is of type [DocumentHighlight[]] - (#DocumentHighlight) or a Thenable that resolves to such.""" + (#DocumentHighlight) or a Thenable that resolves to such. + """ return self.send_request("textDocument/documentHighlight", params) def document_symbol( - self, params: lsp_types.DocumentSymbolParams - ) -> Union[ - List["lsp_types.SymbolInformation"], List["lsp_types.DocumentSymbol"], None - ]: + self, params: lsp_types.DocumentSymbolParams + ) -> list["lsp_types.SymbolInformation"] | list["lsp_types.DocumentSymbol"] | None: """A request to list all symbols found in a given text document. The request's parameter is of type {@link TextDocumentIdentifier} the response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable - that resolves to such.""" + that resolves to such. + """ return self.send_request("textDocument/documentSymbol", params) - def code_action( - self, params: lsp_types.CodeActionParams - ) -> Union[List[Union["lsp_types.Command", "lsp_types.CodeAction"]], None]: + def code_action(self, params: lsp_types.CodeActionParams) -> list[Union["lsp_types.Command", "lsp_types.CodeAction"]] | None: """A request to provide commands for the given text document and range.""" return self.send_request("textDocument/codeAction", params) - def resolve_code_action( - self, params: lsp_types.CodeAction - ) -> "lsp_types.CodeAction": + def resolve_code_action(self, params: lsp_types.CodeAction) -> "lsp_types.CodeAction": """Request to resolve additional information for a given code action.The request's parameter is of type {@link CodeAction} the response - is of type {@link CodeAction} or a Thenable that resolves to such.""" + is of type {@link CodeAction} or a Thenable that resolves to such. + """ return self.send_request("codeAction/resolve", params) def workspace_symbol( - self, params: lsp_types.WorkspaceSymbolParams - ) -> Union[ - List["lsp_types.SymbolInformation"], List["lsp_types.WorkspaceSymbol"], None - ]: + self, params: lsp_types.WorkspaceSymbolParams + ) -> list["lsp_types.SymbolInformation"] | list["lsp_types.WorkspaceSymbol"] | None: """A request to list project-wide symbols matching the query string given by the {@link WorkspaceSymbolParams}. The response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable that @@ -360,76 +320,58 @@ class SolidLspRequest: """ return self.send_request("workspace/symbol", params) - def resolve_workspace_symbol( - self, params: lsp_types.WorkspaceSymbol - ) -> "lsp_types.WorkspaceSymbol": + def resolve_workspace_symbol(self, params: lsp_types.WorkspaceSymbol) -> "lsp_types.WorkspaceSymbol": """A request to resolve the range inside the workspace symbol's location. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_request("workspaceSymbol/resolve", params) - def code_lens( - self, params: lsp_types.CodeLensParams - ) -> Union[List["lsp_types.CodeLens"], None]: + def code_lens(self, params: lsp_types.CodeLensParams) -> list["lsp_types.CodeLens"] | None: """A request to provide code lens for the given text document.""" return self.send_request("textDocument/codeLens", params) - def resolve_code_lens( - self, params: lsp_types.CodeLens - ) -> "lsp_types.CodeLens": + def resolve_code_lens(self, params: lsp_types.CodeLens) -> "lsp_types.CodeLens": """A request to resolve a command for a given code lens.""" return self.send_request("codeLens/resolve", params) - def document_link( - self, params: lsp_types.DocumentLinkParams - ) -> Union[List["lsp_types.DocumentLink"], None]: + def document_link(self, params: lsp_types.DocumentLinkParams) -> list["lsp_types.DocumentLink"] | None: """A request to provide document links""" return self.send_request("textDocument/documentLink", params) - def resolve_document_link( - self, params: lsp_types.DocumentLink - ) -> "lsp_types.DocumentLink": + def resolve_document_link(self, params: lsp_types.DocumentLink) -> "lsp_types.DocumentLink": """Request to resolve additional information for a given document link. The request's parameter is of type {@link DocumentLink} the response - is of type {@link DocumentLink} or a Thenable that resolves to such.""" + is of type {@link DocumentLink} or a Thenable that resolves to such. + """ return self.send_request("documentLink/resolve", params) - def formatting( - self, params: lsp_types.DocumentFormattingParams - ) -> Union[List["lsp_types.TextEdit"], None]: + def formatting(self, params: lsp_types.DocumentFormattingParams) -> list["lsp_types.TextEdit"] | None: """A request to to format a whole document.""" return self.send_request("textDocument/formatting", params) - def range_formatting( - self, params: lsp_types.DocumentRangeFormattingParams - ) -> Union[List["lsp_types.TextEdit"], None]: + def range_formatting(self, params: lsp_types.DocumentRangeFormattingParams) -> list["lsp_types.TextEdit"] | None: """A request to to format a range in a document.""" return self.send_request("textDocument/rangeFormatting", params) - def on_type_formatting( - self, params: lsp_types.DocumentOnTypeFormattingParams - ) -> Union[List["lsp_types.TextEdit"], None]: + def on_type_formatting(self, params: lsp_types.DocumentOnTypeFormattingParams) -> list["lsp_types.TextEdit"] | None: """A request to format a document on type.""" return self.send_request("textDocument/onTypeFormatting", params) - def rename( - self, params: lsp_types.RenameParams - ) -> Union["lsp_types.WorkspaceEdit", None]: + def rename(self, params: lsp_types.RenameParams) -> Union["lsp_types.WorkspaceEdit", None]: """A request to rename a symbol.""" return self.send_request("textDocument/rename", params) - def prepare_rename( - self, params: lsp_types.PrepareRenameParams - ) -> Union["lsp_types.PrepareRenameResult", None]: + def prepare_rename(self, params: lsp_types.PrepareRenameParams) -> Union["lsp_types.PrepareRenameResult", None]: """A request to test and perform the setup necessary for a rename. - @since 3.16 - support for default behavior""" + @since 3.16 - support for default behavior + """ return self.send_request("textDocument/prepareRename", params) - def execute_command( - self, params: lsp_types.ExecuteCommandParams - ) -> Union["lsp_types.LSPAny", None]: + def execute_command(self, params: lsp_types.ExecuteCommandParams) -> Union["lsp_types.LSPAny", None]: """A request send from the client to the server to execute a command. The request might return - a workspace edit which the client will apply to the workspace.""" + a workspace edit which the client will apply to the workspace. + """ return self.send_request("workspace/executeCommand", params) diff --git a/src/multilspy/multilspy_types.py b/src/solidlsp/ls_types.py similarity index 91% rename from src/multilspy/multilspy_types.py rename to src/solidlsp/ls_types.py index dad01167..3d4d4658 100644 --- a/src/multilspy/multilspy_types.py +++ b/src/solidlsp/ls_types.py @@ -4,16 +4,19 @@ Defines wrapper objects around the types returned by LSP to ensure decoupling be from __future__ import annotations -from enum import IntEnum, Enum -from typing_extensions import NotRequired, TypedDict, List, Dict, Union +from enum import Enum, IntEnum +from typing import NotRequired, Union + +from typing_extensions import TypedDict URI = str DocumentUri = str Uint = int RegExp = str + class Position(TypedDict): - """Position in a text document expressed as zero-based line and character + r"""Position in a text document expressed as zero-based line and character offset. Prior to 3.17 the offsets were always based on a UTF-16 string representation. So a string of the form `a𐐀b` the character offset of the character `a` is 0, the character offset of `𐐀` is 1 and the character @@ -39,7 +42,8 @@ class Position(TypedDict): Positions are line end character agnostic. So you can not specify a position that denotes `\r|\n` or `\n|` where `|` represents the character offset. - @since 3.17.0 - support for negotiated position encoding.""" + @since 3.17.0 - support for negotiated position encoding. + """ line: Uint """ Line position in a document (zero-based). @@ -67,7 +71,8 @@ class Range(TypedDict): start: { line: 5, character: 23 } end : { line 6, character : 0 } } - ```""" + ``` + """ start: Position """ The range's start position. """ @@ -77,12 +82,14 @@ class Range(TypedDict): class Location(TypedDict): """Represents a location inside a resource, such as a line - inside a text file.""" + inside a text file. + """ uri: DocumentUri range: Range absolutePath: str - relativePath: Union[str, None] + relativePath: str | None + class CompletionItemKind(IntEnum): """The kind of a completion entry.""" @@ -113,9 +120,11 @@ class CompletionItemKind(IntEnum): Operator = 24 TypeParameter = 25 + class CompletionItem(TypedDict): """A completion item represents a text snippet that is - proposed to complete text that is being typed.""" + proposed to complete text that is being typed. + """ completionText: str """ The completionText of this completion item. @@ -131,6 +140,7 @@ class CompletionItem(TypedDict): """ A human-readable string with additional information about this item, like type or symbol information. """ + class SymbolKind(IntEnum): """A symbol kind.""" @@ -161,17 +171,21 @@ class SymbolKind(IntEnum): Operator = 25 TypeParameter = 26 + class SymbolTag(IntEnum): """Symbol tags are extra annotations that tweak the rendering of a symbol. - @since 3.16""" + @since 3.16 + """ Deprecated = 1 """ Render a symbol as obsolete, usually using a strike-out. """ + class UnifiedSymbolInformation(TypedDict): """Represents information about programming constructs like variables, classes, - interfaces etc.""" + interfaces etc. + """ deprecated: NotRequired[bool] """ Indicates if this symbol is deprecated. @@ -191,7 +205,7 @@ class UnifiedSymbolInformation(TypedDict): """ The name of this symbol. """ kind: SymbolKind """ The kind of this symbol. """ - tags: NotRequired[List[SymbolTag]] + tags: NotRequired[list[SymbolTag]] """ Tags for this symbol. @since 3.16.0 """ @@ -207,7 +221,7 @@ class UnifiedSymbolInformation(TypedDict): detail: NotRequired[str] """ More detail for this symbol, e.g the signature of a function. """ - + range: NotRequired[Range] """ The range enclosing this symbol not including leading/trailing whitespace but everything else like comments. This information is typically used to determine if the clients cursor is @@ -215,37 +229,40 @@ class UnifiedSymbolInformation(TypedDict): selectionRange: NotRequired[Range] """ The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. Must be contained by the `range`. """ - + body: NotRequired[str] """ The body of the symbol. """ - - children: List[UnifiedSymbolInformation] + + children: list[UnifiedSymbolInformation] """ The children of the symbol. Added to be compatible with `lsp_types.DocumentSymbol`, since it is sometimes useful to have the children of the symbol as a user-facing feature.""" - + parent: NotRequired[UnifiedSymbolInformation | None] """The parent of the symbol, if there is any. Added with Serena, not part of the LSP. All symbols except the root packages will have a parent. """ - + class MarkupKind(Enum): """Describes the content type that a client supports in various result literals like `Hover`, `ParameterInfo` or `CompletionItem`. Please note that `MarkupKinds` must not start with a `$`. This kinds - are reserved for internal usage.""" + are reserved for internal usage. + """ PlainText = "plaintext" """ Plain text is supported as a content format """ Markdown = "markdown" """ Markdown is supported as a content format """ + class __MarkedString_Type_1(TypedDict): language: str value: str + MarkedString = Union[str, "__MarkedString_Type_1"] """ MarkedString can be used to render human readable text. It is either a markdown string or a code-block that provides a language and a code snippet. The language identifier @@ -260,8 +277,9 @@ ${value} Note that markdown strings will be sanitized - that means html will be escaped. @deprecated use MarkupContent instead. """ + class MarkupContent(TypedDict): - """A `MarkupContent` literal represents a string value which content is interpreted base on its + r"""A `MarkupContent` literal represents a string value which content is interpreted base on its kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. @@ -282,18 +300,20 @@ class MarkupContent(TypedDict): ``` *Please Note* that clients might sanitize the return markdown. A client could decide to - remove HTML from the markdown to avoid script execution.""" + remove HTML from the markdown to avoid script execution. + """ - kind: "MarkupKind" + kind: MarkupKind """ The type of the Markup """ value: str """ The content itself """ + class Hover(TypedDict): """The result of a hover request.""" - contents: Union["MarkupContent", "MarkedString", List["MarkedString"]] + contents: MarkupContent | MarkedString | list[MarkedString] """ The hover's content """ - range: NotRequired["Range"] + range: NotRequired[Range] """ An optional range inside the text document that is used to - visualize the hover, e.g. by changing the background color. """ \ No newline at end of file + visualize the hover, e.g. by changing the background color. """ diff --git a/src/multilspy/multilspy_utils.py b/src/solidlsp/ls_utils.py similarity index 80% rename from src/multilspy/multilspy_utils.py rename to src/solidlsp/ls_utils.py index 8c54cd69..39a1d914 100644 --- a/src/multilspy/multilspy_utils.py +++ b/src/solidlsp/ls_utils.py @@ -5,19 +5,18 @@ This file contains various utility functions like I/O operations, handling paths import gzip import logging import os -from typing import Tuple, Union -import requests -import shutil -import uuid - import platform +import shutil import subprocess +import uuid from enum import Enum +from pathlib import Path, PurePath -from multilspy.multilspy_exceptions import MultilspyException -from pathlib import PurePath, Path -from multilspy.multilspy_logger import MultilspyLogger -from multilspy.multilspy_types import UnifiedSymbolInformation +import requests + +from solidlsp.ls_exceptions import LanguageServerException +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_types import UnifiedSymbolInformation class InvalidTextLocationError(Exception): @@ -28,8 +27,9 @@ class TextUtils: """ Utilities for text operations. """ + @staticmethod - def get_line_col_from_index(text: str, index: int) -> Tuple[int, int]: + def get_line_col_from_index(text: str, index: int) -> tuple[int, int]: """ Returns the zero-indexed line and column number of the given index in the given text """ @@ -37,7 +37,7 @@ class TextUtils: c = 0 idx = 0 while idx < index: - if text[idx] == '\n': + if text[idx] == "\n": l += 1 c = 0 else: @@ -45,7 +45,7 @@ class TextUtils: idx += 1 return l, c - + @staticmethod def get_index_from_line_col(text: str, line: int, col: int) -> int: """ @@ -60,35 +60,35 @@ class TextUtils: idx += 1 idx += col return idx - + @staticmethod - def _get_updated_position_from_line_and_column_and_edit(l: int, c: int, text_to_be_inserted: str) -> Tuple[int, int]: + def _get_updated_position_from_line_and_column_and_edit(l: int, c: int, text_to_be_inserted: str) -> tuple[int, int]: """ Utility function to get the position of the cursor after inserting text at a given line and column. """ - num_newlines_in_gen_text = text_to_be_inserted.count('\n') + num_newlines_in_gen_text = text_to_be_inserted.count("\n") if num_newlines_in_gen_text > 0: l += num_newlines_in_gen_text - c = len(text_to_be_inserted.split('\n')[-1]) + c = len(text_to_be_inserted.split("\n")[-1]) else: c += len(text_to_be_inserted) return (l, c) - + @staticmethod - def delete_text_between_positions(text: str, start_line: int, start_col: int, end_line: int, end_col: int) -> Tuple[str, str]: + def delete_text_between_positions(text: str, start_line: int, start_col: int, end_line: int, end_col: int) -> tuple[str, str]: """ Deletes the text between the given start and end positions. Returns the modified text and the deleted text. """ del_start_idx = TextUtils.get_index_from_line_col(text, start_line, start_col) del_end_idx = TextUtils.get_index_from_line_col(text, end_line, end_col) - + deleted_text = text[del_start_idx:del_end_idx] new_text = text[:del_start_idx] + text[del_end_idx:] return new_text, deleted_text - + @staticmethod - def insert_text_at_position(text: str, line: int, col: int, text_to_be_inserted: str) -> Tuple[str, int, int]: + def insert_text_at_position(text: str, line: int, col: int, text_to_be_inserted: str) -> tuple[str, int, int]: """ Inserts the given text at the given line and column. Returns the modified text and the new line and column. @@ -113,6 +113,7 @@ class PathUtils: """ Utilities for platform-agnostic path operations. """ + @staticmethod def uri_to_path(uri: str) -> str: """ @@ -121,14 +122,15 @@ class PathUtils: This method was obtained from https://stackoverflow.com/a/61922504 """ try: - from urllib.parse import urlparse, unquote + from urllib.parse import unquote, urlparse from urllib.request import url2pathname except ImportError: # backwards compatibility - from urlparse import urlparse from urllib import unquote, url2pathname + + from urlparse import urlparse parsed = urlparse(uri) - host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc) + host = f"{os.path.sep}{os.path.sep}{parsed.netloc}{os.path.sep}" return os.path.normpath(os.path.join(host, url2pathname(unquote(parsed.path)))) @staticmethod @@ -141,10 +143,10 @@ class PathUtils: @staticmethod def is_glob_pattern(pattern: str) -> bool: """Check if a pattern contains glob-specific characters.""" - return any(c in pattern for c in '*?[]!') + return any(c in pattern for c in "*?[]!") @staticmethod - def get_relative_path(path: str, base_path: str) -> Union[str, None]: + def get_relative_path(path: str, base_path: str) -> str | None: """ Gets relative path if it's possible (paths should be on the same drive), returns `None` otherwise. @@ -160,22 +162,22 @@ class FileUtils: """ @staticmethod - def read_file(logger: MultilspyLogger, file_path: str) -> str: + def read_file(logger: LanguageServerLogger, file_path: str) -> str: """ Reads the file at the given path and returns the contents as a string. """ if not os.path.exists(file_path): logger.log(f"File read '{file_path}' failed: File does not exist.", logging.ERROR) - raise MultilspyException(f"File read '{file_path}' failed: File does not exist.") + raise LanguageServerException(f"File read '{file_path}' failed: File does not exist.") try: - with open(file_path, "r", encoding="utf-8") as inp_file: + with open(file_path, encoding="utf-8") as inp_file: return inp_file.read() except Exception as exc: logger.log(f"File read '{file_path}' failed to read with encoding 'utf-8': {exc}", logging.ERROR) - raise MultilspyException("File read failed.") from None - + raise LanguageServerException("File read failed.") from None + @staticmethod - def download_file(logger: MultilspyLogger, url: str, target_path: str) -> None: + def download_file(logger: LanguageServerLogger, url: str, target_path: str) -> None: """ Downloads the file from the given URL to the given {target_path} """ @@ -183,15 +185,15 @@ class FileUtils: 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) - raise MultilspyException("Error downloading file.") + raise LanguageServerException("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) - raise MultilspyException("Error downloading file.") from None + raise LanguageServerException("Error downloading file.") from None @staticmethod - def download_and_extract_archive(logger: MultilspyLogger, url: str, target_path: str, archive_type: str) -> None: + def download_and_extract_archive(logger: LanguageServerLogger, 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} """ @@ -216,10 +218,10 @@ class FileUtils: shutil.copyfileobj(f_in, f_out) else: logger.log(f"Unknown archive type '{archive_type}' for extraction", logging.ERROR) - raise MultilspyException(f"Unknown archive type '{archive_type}'") + raise LanguageServerException(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) - raise MultilspyException("Error extracting archive.") from exc + raise LanguageServerException("Error extracting archive.") from exc finally: for tmp_file_name in tmp_files: if os.path.exists(tmp_file_name): @@ -230,6 +232,7 @@ class PlatformId(str, Enum): """ multilspy supported platforms """ + WIN_x86 = "win-x86" WIN_x64 = "win-x64" WIN_arm64 = "win-arm64" @@ -247,6 +250,7 @@ class DotnetVersion(str, Enum): """ multilspy supported dotnet versions """ + V4 = "4" V6 = "6" V7 = "7" @@ -275,11 +279,11 @@ class PlatformUtils: platform_id = system_map[system] + "-" + machine_map[machine] if system == "Linux" and bitness == "64bit": libc = platform.libc_ver()[0] - if libc != 'glibc': + if libc != "glibc": platform_id += "-" + libc return PlatformId(platform_id) else: - raise MultilspyException(f"Unknown platform: {system=}, {machine=}, {bitness=}") + raise LanguageServerException(f"Unknown platform: {system=}, {machine=}, {bitness=}") @staticmethod def _determine_windows_machine_type(): @@ -289,13 +293,13 @@ class PlatformUtils: class SYSTEM_INFO(ctypes.Structure): class _U(ctypes.Union): class _S(ctypes.Structure): - _fields_ = [("wProcessorArchitecture", wintypes.WORD), - ("wReserved", wintypes.WORD)] - _fields_ = [("dwOemId", wintypes.DWORD), - ("s", _S)] + _fields_ = [("wProcessorArchitecture", wintypes.WORD), ("wReserved", wintypes.WORD)] + + _fields_ = [("dwOemId", wintypes.DWORD), ("s", _S)] _anonymous_ = ("s",) - _fields_ = [("u", _U), + _fields_ = [ + ("u", _U), ("dwPageSize", wintypes.DWORD), ("lpMinimumApplicationAddress", wintypes.LPVOID), ("lpMaximumApplicationAddress", wintypes.LPVOID), @@ -304,22 +308,22 @@ class PlatformUtils: ("dwProcessorType", wintypes.DWORD), ("dwAllocationGranularity", wintypes.DWORD), ("wProcessorLevel", wintypes.WORD), - ("wProcessorRevision", wintypes.WORD)] + ("wProcessorRevision", wintypes.WORD), + ] _anonymous_ = ("u",) sys_info = SYSTEM_INFO() ctypes.windll.kernel32.GetNativeSystemInfo(ctypes.byref(sys_info)) arch_map = { - 9: 'AMD64', - 5: 'ARM', - 12: 'arm64', - 6: 'Intel Itanium-based', - 0: 'i386', + 9: "AMD64", + 5: "ARM", + 12: "arm64", + 6: "Intel Itanium-based", + 0: "i386", } - return arch_map.get(sys_info.wProcessorArchitecture, f'Unknown ({sys_info.wProcessorArchitecture})') - + return arch_map.get(sys_info.wProcessorArchitecture, f"Unknown ({sys_info.wProcessorArchitecture})") @staticmethod def get_dotnet_version() -> DotnetVersion: @@ -329,14 +333,14 @@ class PlatformUtils: try: result = subprocess.run(["dotnet", "--list-runtimes"], capture_output=True, check=True) available_version_cmd_output = [] - for line in result.stdout.decode('utf-8').split('\n'): - if line.startswith('Microsoft.NETCore.App'): - version_cmd_output = line.split(' ')[1] + for line in result.stdout.decode("utf-8").split("\n"): + if line.startswith("Microsoft.NETCore.App"): + version_cmd_output = line.split(" ")[1] available_version_cmd_output.append(version_cmd_output) - + if not available_version_cmd_output: - raise MultilspyException("dotnet not found on the system") - + raise LanguageServerException("dotnet not found on the system") + # Check for supported versions in order of preference (latest first) for version_cmd_output in available_version_cmd_output: if version_cmd_output.startswith("8"): @@ -350,16 +354,17 @@ class PlatformUtils: for version_cmd_output in available_version_cmd_output: if version_cmd_output.startswith("4"): return DotnetVersion.V4 - + # If no supported version found, raise exception with all available versions - raise MultilspyException(f"No supported dotnet version found. Available versions: {', '.join(available_version_cmd_output)}. Supported versions: 4, 6, 7, 8") + raise LanguageServerException( + f"No supported dotnet version found. Available versions: {', '.join(available_version_cmd_output)}. Supported versions: 4, 6, 7, 8" + ) except (FileNotFoundError, subprocess.CalledProcessError): try: result = subprocess.run(["mono", "--version"], capture_output=True, check=True) return DotnetVersion.VMONO except (FileNotFoundError, subprocess.CalledProcessError): - raise MultilspyException("dotnet or mono not found on the system") - + raise LanguageServerException("dotnet or mono not found on the system") class SymbolUtils: diff --git a/src/multilspy/lsp_protocol_handler/lsp_constants.py b/src/solidlsp/lsp_protocol_handler/lsp_constants.py similarity index 99% rename from src/multilspy/lsp_protocol_handler/lsp_constants.py rename to src/solidlsp/lsp_protocol_handler/lsp_constants.py index 4ca5c3d1..329c0039 100644 --- a/src/multilspy/lsp_protocol_handler/lsp_constants.py +++ b/src/solidlsp/lsp_protocol_handler/lsp_constants.py @@ -2,6 +2,7 @@ This module contains constants used in the LSP protocol. """ + class LSPConstants: """ This class contains constants used in the LSP protocol. diff --git a/src/multilspy/lsp_protocol_handler/lsp_requests.py b/src/solidlsp/lsp_protocol_handler/lsp_requests.py similarity index 69% rename from src/multilspy/lsp_protocol_handler/lsp_requests.py rename to src/solidlsp/lsp_protocol_handler/lsp_requests.py index 852df21d..383e4566 100644 --- a/src/multilspy/lsp_protocol_handler/lsp_requests.py +++ b/src/solidlsp/lsp_protocol_handler/lsp_requests.py @@ -29,8 +29,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from typing import List, Union -from multilspy.lsp_protocol_handler import lsp_types +from typing import Union + +from solidlsp.lsp_protocol_handler import lsp_types + class LspRequest: def __init__(self, send_request): @@ -38,96 +40,94 @@ class LspRequest: async def implementation( self, params: lsp_types.ImplementationParams - ) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]: + ) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]: """A request to resolve the implementation locations of a symbol at a given text document position. The request's parameter is of type [TextDocumentPositionParams] (#TextDocumentPositionParams) the response is of type {@link Definition} or a - Thenable that resolves to such.""" + Thenable that resolves to such. + """ return await self.send_request("textDocument/implementation", params) async def type_definition( self, params: lsp_types.TypeDefinitionParams - ) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]: + ) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]: """A request to resolve the type definition locations of a symbol at a given text document position. The request's parameter is of type [TextDocumentPositionParams] (#TextDocumentPositionParams) the response is of type {@link Definition} or a - Thenable that resolves to such.""" + Thenable that resolves to such. + """ return await self.send_request("textDocument/typeDefinition", params) - async def document_color( - self, params: lsp_types.DocumentColorParams - ) -> List["lsp_types.ColorInformation"]: + async def document_color(self, params: lsp_types.DocumentColorParams) -> list["lsp_types.ColorInformation"]: """A request to list all color symbols found in a given text document. The request's parameter is of type {@link DocumentColorParams} the response is of type {@link ColorInformation ColorInformation[]} or a Thenable - that resolves to such.""" + that resolves to such. + """ return await self.send_request("textDocument/documentColor", params) - async def color_presentation( - self, params: lsp_types.ColorPresentationParams - ) -> List["lsp_types.ColorPresentation"]: + async def color_presentation(self, params: lsp_types.ColorPresentationParams) -> list["lsp_types.ColorPresentation"]: """A request to list all presentation for a color. The request's parameter is of type {@link ColorPresentationParams} the response is of type {@link ColorInformation ColorInformation[]} or a Thenable - that resolves to such.""" + that resolves to such. + """ return await self.send_request("textDocument/colorPresentation", params) - async def folding_range( - self, params: lsp_types.FoldingRangeParams - ) -> Union[List["lsp_types.FoldingRange"], None]: + async def folding_range(self, params: lsp_types.FoldingRangeParams) -> list["lsp_types.FoldingRange"] | None: """A request to provide folding ranges in a document. The request's parameter is of type {@link FoldingRangeParams}, the response is of type {@link FoldingRangeList} or a Thenable - that resolves to such.""" + that resolves to such. + """ return await self.send_request("textDocument/foldingRange", params) async def declaration( self, params: lsp_types.DeclarationParams - ) -> Union["lsp_types.Declaration", List["lsp_types.LocationLink"], None]: + ) -> Union["lsp_types.Declaration", list["lsp_types.LocationLink"], None]: """A request to resolve the type definition locations of a symbol at a given text document position. The request's parameter is of type [TextDocumentPositionParams] (#TextDocumentPositionParams) the response is of type {@link Declaration} or a typed array of {@link DeclarationLink} or a Thenable that resolves - to such.""" + to such. + """ return await self.send_request("textDocument/declaration", params) - async def selection_range( - self, params: lsp_types.SelectionRangeParams - ) -> Union[List["lsp_types.SelectionRange"], None]: + async def selection_range(self, params: lsp_types.SelectionRangeParams) -> list["lsp_types.SelectionRange"] | None: """A request to provide selection ranges in a document. The request's parameter is of type {@link SelectionRangeParams}, the response is of type {@link SelectionRange SelectionRange[]} or a Thenable - that resolves to such.""" + that resolves to such. + """ return await self.send_request("textDocument/selectionRange", params) - async def prepare_call_hierarchy( - self, params: lsp_types.CallHierarchyPrepareParams - ) -> Union[List["lsp_types.CallHierarchyItem"], None]: + async def prepare_call_hierarchy(self, params: lsp_types.CallHierarchyPrepareParams) -> list["lsp_types.CallHierarchyItem"] | None: """A request to result a `CallHierarchyItem` in a document at a given position. Can be used as an input to an incoming or outgoing call hierarchy. - @since 3.16.0""" + @since 3.16.0 + """ return await self.send_request("textDocument/prepareCallHierarchy", params) async def incoming_calls( self, params: lsp_types.CallHierarchyIncomingCallsParams - ) -> Union[List["lsp_types.CallHierarchyIncomingCall"], None]: + ) -> list["lsp_types.CallHierarchyIncomingCall"] | None: """A request to resolve the incoming calls for a given `CallHierarchyItem`. - @since 3.16.0""" + @since 3.16.0 + """ return await self.send_request("callHierarchy/incomingCalls", params) async def outgoing_calls( self, params: lsp_types.CallHierarchyOutgoingCallsParams - ) -> Union[List["lsp_types.CallHierarchyOutgoingCall"], None]: + ) -> list["lsp_types.CallHierarchyOutgoingCall"] | None: """A request to resolve the outgoing calls for a given `CallHierarchyItem`. - @since 3.16.0""" + @since 3.16.0 + """ return await self.send_request("callHierarchy/outgoingCalls", params) - async def semantic_tokens_full( - self, params: lsp_types.SemanticTokensParams - ) -> Union["lsp_types.SemanticTokens", None]: + async def semantic_tokens_full(self, params: lsp_types.SemanticTokensParams) -> Union["lsp_types.SemanticTokens", None]: """@since 3.16.0""" return await self.send_request("textDocument/semanticTokens/full", params) @@ -137,157 +137,143 @@ class LspRequest: """@since 3.16.0""" return await self.send_request("textDocument/semanticTokens/full/delta", params) - async def semantic_tokens_range( - self, params: lsp_types.SemanticTokensRangeParams - ) -> Union["lsp_types.SemanticTokens", None]: + async def semantic_tokens_range(self, params: lsp_types.SemanticTokensRangeParams) -> Union["lsp_types.SemanticTokens", None]: """@since 3.16.0""" return await self.send_request("textDocument/semanticTokens/range", params) - async def linked_editing_range( - self, params: lsp_types.LinkedEditingRangeParams - ) -> Union["lsp_types.LinkedEditingRanges", None]: + async def linked_editing_range(self, params: lsp_types.LinkedEditingRangeParams) -> Union["lsp_types.LinkedEditingRanges", None]: """A request to provide ranges that can be edited together. - @since 3.16.0""" + @since 3.16.0 + """ return await self.send_request("textDocument/linkedEditingRange", params) - async def will_create_files( - self, params: lsp_types.CreateFilesParams - ) -> Union["lsp_types.WorkspaceEdit", None]: + async def will_create_files(self, params: lsp_types.CreateFilesParams) -> Union["lsp_types.WorkspaceEdit", None]: """The will create files request is sent from the client to the server before files are actually created as long as the creation is triggered from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return await self.send_request("workspace/willCreateFiles", params) - async def will_rename_files( - self, params: lsp_types.RenameFilesParams - ) -> Union["lsp_types.WorkspaceEdit", None]: + async def will_rename_files(self, params: lsp_types.RenameFilesParams) -> Union["lsp_types.WorkspaceEdit", None]: """The will rename files request is sent from the client to the server before files are actually renamed as long as the rename is triggered from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return await self.send_request("workspace/willRenameFiles", params) - async def will_delete_files( - self, params: lsp_types.DeleteFilesParams - ) -> Union["lsp_types.WorkspaceEdit", None]: + async def will_delete_files(self, params: lsp_types.DeleteFilesParams) -> Union["lsp_types.WorkspaceEdit", None]: """The did delete files notification is sent from the client to the server when files were deleted from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return await self.send_request("workspace/willDeleteFiles", params) - async def moniker( - self, params: lsp_types.MonikerParams - ) -> Union[List["lsp_types.Moniker"], None]: + async def moniker(self, params: lsp_types.MonikerParams) -> list["lsp_types.Moniker"] | None: """A request to get the moniker of a symbol at a given text document position. The request parameter is of type {@link TextDocumentPositionParams}. - The response is of type {@link Moniker Moniker[]} or `null`.""" + The response is of type {@link Moniker Moniker[]} or `null`. + """ return await self.send_request("textDocument/moniker", params) - async def prepare_type_hierarchy( - self, params: lsp_types.TypeHierarchyPrepareParams - ) -> Union[List["lsp_types.TypeHierarchyItem"], None]: + async def prepare_type_hierarchy(self, params: lsp_types.TypeHierarchyPrepareParams) -> list["lsp_types.TypeHierarchyItem"] | None: """A request to result a `TypeHierarchyItem` in a document at a given position. Can be used as an input to a subtypes or supertypes type hierarchy. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("textDocument/prepareTypeHierarchy", params) async def type_hierarchy_supertypes( self, params: lsp_types.TypeHierarchySupertypesParams - ) -> Union[List["lsp_types.TypeHierarchyItem"], None]: + ) -> list["lsp_types.TypeHierarchyItem"] | None: """A request to resolve the supertypes for a given `TypeHierarchyItem`. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("typeHierarchy/supertypes", params) - async def type_hierarchy_subtypes( - self, params: lsp_types.TypeHierarchySubtypesParams - ) -> Union[List["lsp_types.TypeHierarchyItem"], None]: + async def type_hierarchy_subtypes(self, params: lsp_types.TypeHierarchySubtypesParams) -> list["lsp_types.TypeHierarchyItem"] | None: """A request to resolve the subtypes for a given `TypeHierarchyItem`. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("typeHierarchy/subtypes", params) - async def inline_value( - self, params: lsp_types.InlineValueParams - ) -> Union[List["lsp_types.InlineValue"], None]: + async def inline_value(self, params: lsp_types.InlineValueParams) -> list["lsp_types.InlineValue"] | None: """A request to provide inline values in a document. The request's parameter is of type {@link InlineValueParams}, the response is of type {@link InlineValue InlineValue[]} or a Thenable that resolves to such. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("textDocument/inlineValue", params) - async def inlay_hint( - self, params: lsp_types.InlayHintParams - ) -> Union[List["lsp_types.InlayHint"], None]: + async def inlay_hint(self, params: lsp_types.InlayHintParams) -> list["lsp_types.InlayHint"] | None: """A request to provide inlay hints in a document. The request's parameter is of type {@link InlayHintsParams}, the response is of type {@link InlayHint InlayHint[]} or a Thenable that resolves to such. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("textDocument/inlayHint", params) - async def resolve_inlay_hint( - self, params: lsp_types.InlayHint - ) -> "lsp_types.InlayHint": + async def resolve_inlay_hint(self, params: lsp_types.InlayHint) -> "lsp_types.InlayHint": """A request to resolve additional properties for an inlay hint. The request's parameter is of type {@link InlayHint}, the response is of type {@link InlayHint} or a Thenable that resolves to such. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("inlayHint/resolve", params) - async def text_document_diagnostic( - self, params: lsp_types.DocumentDiagnosticParams - ) -> "lsp_types.DocumentDiagnosticReport": + async def text_document_diagnostic(self, params: lsp_types.DocumentDiagnosticParams) -> "lsp_types.DocumentDiagnosticReport": """The document diagnostic request definition. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("textDocument/diagnostic", params) - async def workspace_diagnostic( - self, params: lsp_types.WorkspaceDiagnosticParams - ) -> "lsp_types.WorkspaceDiagnosticReport": + async def workspace_diagnostic(self, params: lsp_types.WorkspaceDiagnosticParams) -> "lsp_types.WorkspaceDiagnosticReport": """The workspace diagnostic request definition. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("workspace/diagnostic", params) - async def initialize( - self, params: lsp_types.InitializeParams - ) -> "lsp_types.InitializeResult": + async def initialize(self, params: lsp_types.InitializeParams) -> "lsp_types.InitializeResult": """The initialize request is sent from the client to the server. It is sent once as the request after starting up the server. The requests parameter is of type {@link InitializeParams} the response if of type {@link InitializeResult} of a Thenable that - resolves to such.""" + resolves to such. + """ return await self.send_request("initialize", params) async def shutdown(self) -> None: """A shutdown request is sent from the client to the server. It is sent once when the client decides to shutdown the server. The only notification that is sent after a shutdown request - is the exit event.""" + is the exit event. + """ return await self.send_request("shutdown") - async def will_save_wait_until( - self, params: lsp_types.WillSaveTextDocumentParams - ) -> Union[List["lsp_types.TextEdit"], None]: + async def will_save_wait_until(self, params: lsp_types.WillSaveTextDocumentParams) -> list["lsp_types.TextEdit"] | None: """A document will save request is sent from the client to the server before the document is actually saved. The request can return an array of TextEdits which will be applied to the text document before it is saved. Please note that clients might drop results if computing the text edits took too long or if a server constantly fails on this request. This is done to keep the save fast and - reliable.""" + reliable. + """ return await self.send_request("textDocument/willSaveWaitUntil", params) async def completion( self, params: lsp_types.CompletionParams - ) -> Union[List["lsp_types.CompletionItem"], "lsp_types.CompletionList", None]: + ) -> Union[list["lsp_types.CompletionItem"], "lsp_types.CompletionList", None]: """Request to request completion at a given text document position. The request's parameter is of type {@link TextDocumentPosition} the response is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList} @@ -300,85 +286,72 @@ class LspRequest: """ return await self.send_request("textDocument/completion", params) - async def resolve_completion_item( - self, params: lsp_types.CompletionItem - ) -> "lsp_types.CompletionItem": + async def resolve_completion_item(self, params: lsp_types.CompletionItem) -> "lsp_types.CompletionItem": """Request to resolve additional information for a given completion item.The request's parameter is of type {@link CompletionItem} the response - is of type {@link CompletionItem} or a Thenable that resolves to such.""" + is of type {@link CompletionItem} or a Thenable that resolves to such. + """ return await self.send_request("completionItem/resolve", params) - async def hover( - self, params: lsp_types.HoverParams - ) -> Union["lsp_types.Hover", None]: + async def hover(self, params: lsp_types.HoverParams) -> Union["lsp_types.Hover", None]: """Request to request hover information at a given text document position. The request's parameter is of type {@link TextDocumentPosition} the response is of - type {@link Hover} or a Thenable that resolves to such.""" + type {@link Hover} or a Thenable that resolves to such. + """ return await self.send_request("textDocument/hover", params) - async def signature_help( - self, params: lsp_types.SignatureHelpParams - ) -> Union["lsp_types.SignatureHelp", None]: + async def signature_help(self, params: lsp_types.SignatureHelpParams) -> Union["lsp_types.SignatureHelp", None]: return await self.send_request("textDocument/signatureHelp", params) - async def definition( - self, params: lsp_types.DefinitionParams - ) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]: + async def definition(self, params: lsp_types.DefinitionParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]: """A request to resolve the definition location of a symbol at a given text document position. The request's parameter is of type [TextDocumentPosition] (#TextDocumentPosition) the response is of either type {@link Definition} or a typed array of {@link DefinitionLink} or a Thenable that resolves - to such.""" + to such. + """ return await self.send_request("textDocument/definition", params) - async def references( - self, params: lsp_types.ReferenceParams - ) -> Union[List["lsp_types.Location"], None]: + async def references(self, params: lsp_types.ReferenceParams) -> list["lsp_types.Location"] | None: """A request to resolve project-wide references for the symbol denoted by the given text document position. The request's parameter is of type {@link ReferenceParams} the response is of type - {@link Location Location[]} or a Thenable that resolves to such.""" + {@link Location Location[]} or a Thenable that resolves to such. + """ return await self.send_request("textDocument/references", params) - async def document_highlight( - self, params: lsp_types.DocumentHighlightParams - ) -> Union[List["lsp_types.DocumentHighlight"], None]: + async def document_highlight(self, params: lsp_types.DocumentHighlightParams) -> list["lsp_types.DocumentHighlight"] | None: """Request to resolve a {@link DocumentHighlight} for a given text document position. The request's parameter is of type [TextDocumentPosition] (#TextDocumentPosition) the request response is of type [DocumentHighlight[]] - (#DocumentHighlight) or a Thenable that resolves to such.""" + (#DocumentHighlight) or a Thenable that resolves to such. + """ return await self.send_request("textDocument/documentHighlight", params) async def document_symbol( self, params: lsp_types.DocumentSymbolParams - ) -> Union[ - List["lsp_types.SymbolInformation"], List["lsp_types.DocumentSymbol"], None - ]: + ) -> list["lsp_types.SymbolInformation"] | list["lsp_types.DocumentSymbol"] | None: """A request to list all symbols found in a given text document. The request's parameter is of type {@link TextDocumentIdentifier} the response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable - that resolves to such.""" + that resolves to such. + """ return await self.send_request("textDocument/documentSymbol", params) - async def code_action( - self, params: lsp_types.CodeActionParams - ) -> Union[List[Union["lsp_types.Command", "lsp_types.CodeAction"]], None]: + async def code_action(self, params: lsp_types.CodeActionParams) -> list[Union["lsp_types.Command", "lsp_types.CodeAction"]] | None: """A request to provide commands for the given text document and range.""" return await self.send_request("textDocument/codeAction", params) - async def resolve_code_action( - self, params: lsp_types.CodeAction - ) -> "lsp_types.CodeAction": + async def resolve_code_action(self, params: lsp_types.CodeAction) -> "lsp_types.CodeAction": """Request to resolve additional information for a given code action.The request's parameter is of type {@link CodeAction} the response - is of type {@link CodeAction} or a Thenable that resolves to such.""" + is of type {@link CodeAction} or a Thenable that resolves to such. + """ return await self.send_request("codeAction/resolve", params) async def workspace_symbol( self, params: lsp_types.WorkspaceSymbolParams - ) -> Union[ - List["lsp_types.SymbolInformation"], List["lsp_types.WorkspaceSymbol"], None - ]: + ) -> list["lsp_types.SymbolInformation"] | list["lsp_types.WorkspaceSymbol"] | None: """A request to list project-wide symbols matching the query string given by the {@link WorkspaceSymbolParams}. The response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable that @@ -390,78 +363,60 @@ class LspRequest: """ return await self.send_request("workspace/symbol", params) - async def resolve_workspace_symbol( - self, params: lsp_types.WorkspaceSymbol - ) -> "lsp_types.WorkspaceSymbol": + async def resolve_workspace_symbol(self, params: lsp_types.WorkspaceSymbol) -> "lsp_types.WorkspaceSymbol": """A request to resolve the range inside the workspace symbol's location. - @since 3.17.0""" + @since 3.17.0 + """ return await self.send_request("workspaceSymbol/resolve", params) - async def code_lens( - self, params: lsp_types.CodeLensParams - ) -> Union[List["lsp_types.CodeLens"], None]: + async def code_lens(self, params: lsp_types.CodeLensParams) -> list["lsp_types.CodeLens"] | None: """A request to provide code lens for the given text document.""" return await self.send_request("textDocument/codeLens", params) - async def resolve_code_lens( - self, params: lsp_types.CodeLens - ) -> "lsp_types.CodeLens": + async def resolve_code_lens(self, params: lsp_types.CodeLens) -> "lsp_types.CodeLens": """A request to resolve a command for a given code lens.""" return await self.send_request("codeLens/resolve", params) - async def document_link( - self, params: lsp_types.DocumentLinkParams - ) -> Union[List["lsp_types.DocumentLink"], None]: + async def document_link(self, params: lsp_types.DocumentLinkParams) -> list["lsp_types.DocumentLink"] | None: """A request to provide document links""" return await self.send_request("textDocument/documentLink", params) - async def resolve_document_link( - self, params: lsp_types.DocumentLink - ) -> "lsp_types.DocumentLink": + async def resolve_document_link(self, params: lsp_types.DocumentLink) -> "lsp_types.DocumentLink": """Request to resolve additional information for a given document link. The request's parameter is of type {@link DocumentLink} the response - is of type {@link DocumentLink} or a Thenable that resolves to such.""" + is of type {@link DocumentLink} or a Thenable that resolves to such. + """ return await self.send_request("documentLink/resolve", params) - async def formatting( - self, params: lsp_types.DocumentFormattingParams - ) -> Union[List["lsp_types.TextEdit"], None]: + async def formatting(self, params: lsp_types.DocumentFormattingParams) -> list["lsp_types.TextEdit"] | None: """A request to to format a whole document.""" return await self.send_request("textDocument/formatting", params) - async def range_formatting( - self, params: lsp_types.DocumentRangeFormattingParams - ) -> Union[List["lsp_types.TextEdit"], None]: + async def range_formatting(self, params: lsp_types.DocumentRangeFormattingParams) -> list["lsp_types.TextEdit"] | None: """A request to to format a range in a document.""" return await self.send_request("textDocument/rangeFormatting", params) - async def on_type_formatting( - self, params: lsp_types.DocumentOnTypeFormattingParams - ) -> Union[List["lsp_types.TextEdit"], None]: + async def on_type_formatting(self, params: lsp_types.DocumentOnTypeFormattingParams) -> list["lsp_types.TextEdit"] | None: """A request to format a document on type.""" return await self.send_request("textDocument/onTypeFormatting", params) - async def rename( - self, params: lsp_types.RenameParams - ) -> Union["lsp_types.WorkspaceEdit", None]: + async def rename(self, params: lsp_types.RenameParams) -> Union["lsp_types.WorkspaceEdit", None]: """A request to rename a symbol.""" return await self.send_request("textDocument/rename", params) - async def prepare_rename( - self, params: lsp_types.PrepareRenameParams - ) -> Union["lsp_types.PrepareRenameResult", None]: + async def prepare_rename(self, params: lsp_types.PrepareRenameParams) -> Union["lsp_types.PrepareRenameResult", None]: """A request to test and perform the setup necessary for a rename. - @since 3.16 - support for default behavior""" + @since 3.16 - support for default behavior + """ return await self.send_request("textDocument/prepareRename", params) - async def execute_command( - self, params: lsp_types.ExecuteCommandParams - ) -> Union["lsp_types.LSPAny", None]: + async def execute_command(self, params: lsp_types.ExecuteCommandParams) -> Union["lsp_types.LSPAny", None]: """A request send from the client to the server to execute a command. The request might return - a workspace edit which the client will apply to the workspace.""" + a workspace edit which the client will apply to the workspace. + """ return await self.send_request("workspace/executeCommand", params) @@ -469,92 +424,87 @@ class LspNotification: def __init__(self, send_notification): self.send_notification = send_notification - def did_change_workspace_folders( - self, params: lsp_types.DidChangeWorkspaceFoldersParams - ) -> None: + def did_change_workspace_folders(self, params: lsp_types.DidChangeWorkspaceFoldersParams) -> None: """The `workspace/didChangeWorkspaceFolders` notification is sent from the client to the server when the workspace - folder configuration changes.""" + folder configuration changes. + """ return self.send_notification("workspace/didChangeWorkspaceFolders", params) - def cancel_work_done_progress( - self, params: lsp_types.WorkDoneProgressCancelParams - ) -> None: + def cancel_work_done_progress(self, params: lsp_types.WorkDoneProgressCancelParams) -> None: """The `window/workDoneProgress/cancel` notification is sent from the client to the server to cancel a progress - initiated on the server side.""" + initiated on the server side. + """ return self.send_notification("window/workDoneProgress/cancel", params) def did_create_files(self, params: lsp_types.CreateFilesParams) -> None: """The did create files notification is sent from the client to the server when files were created from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_notification("workspace/didCreateFiles", params) def did_rename_files(self, params: lsp_types.RenameFilesParams) -> None: """The did rename files notification is sent from the client to the server when files were renamed from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_notification("workspace/didRenameFiles", params) def did_delete_files(self, params: lsp_types.DeleteFilesParams) -> None: """The will delete files request is sent from the client to the server before files are actually deleted as long as the deletion is triggered from within the client. - @since 3.16.0""" + @since 3.16.0 + """ return self.send_notification("workspace/didDeleteFiles", params) - def did_open_notebook_document( - self, params: lsp_types.DidOpenNotebookDocumentParams - ) -> None: + def did_open_notebook_document(self, params: lsp_types.DidOpenNotebookDocumentParams) -> None: """A notification sent when a notebook opens. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_notification("notebookDocument/didOpen", params) - def did_change_notebook_document( - self, params: lsp_types.DidChangeNotebookDocumentParams - ) -> None: + def did_change_notebook_document(self, params: lsp_types.DidChangeNotebookDocumentParams) -> None: return self.send_notification("notebookDocument/didChange", params) - def did_save_notebook_document( - self, params: lsp_types.DidSaveNotebookDocumentParams - ) -> None: + def did_save_notebook_document(self, params: lsp_types.DidSaveNotebookDocumentParams) -> None: """A notification sent when a notebook document is saved. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_notification("notebookDocument/didSave", params) - def did_close_notebook_document( - self, params: lsp_types.DidCloseNotebookDocumentParams - ) -> None: + def did_close_notebook_document(self, params: lsp_types.DidCloseNotebookDocumentParams) -> None: """A notification sent when a notebook closes. - @since 3.17.0""" + @since 3.17.0 + """ return self.send_notification("notebookDocument/didClose", params) def initialized(self, params: lsp_types.InitializedParams) -> None: """The initialized notification is sent from the client to the server after the client is fully initialized and the server - is allowed to send requests from the server to the client.""" + is allowed to send requests from the server to the client. + """ return self.send_notification("initialized", params) def exit(self) -> None: """The exit event is sent from the client to the server to - ask the server to exit its process.""" + ask the server to exit its process. + """ return self.send_notification("exit") - def workspace_did_change_configuration( - self, params: lsp_types.DidChangeConfigurationParams - ) -> None: + def workspace_did_change_configuration(self, params: lsp_types.DidChangeConfigurationParams) -> None: """The configuration change notification is sent from the client to the server when the client's configuration has changed. The notification contains - the changed configuration as defined by the language client.""" + the changed configuration as defined by the language client. + """ return self.send_notification("workspace/didChangeConfiguration", params) - def did_open_text_document( - self, params: lsp_types.DidOpenTextDocumentParams - ) -> None: + def did_open_text_document(self, params: lsp_types.DidOpenTextDocumentParams) -> None: """The document open notification is sent from the client to the server to signal newly opened text documents. The document's truth is now managed by the client and the server must not try to read the document's truth using the document's @@ -562,47 +512,43 @@ class LspNotification: mean that its content is presented in an editor. An open notification must not be sent more than once without a corresponding close notification send before. This means open and close notification must be balanced and the max open count - is one.""" + is one. + """ return self.send_notification("textDocument/didOpen", params) - def did_change_text_document( - self, params: lsp_types.DidChangeTextDocumentParams - ) -> None: + def did_change_text_document(self, params: lsp_types.DidChangeTextDocumentParams) -> None: """The document change notification is sent from the client to the server to signal - changes to a text document.""" + changes to a text document. + """ return self.send_notification("textDocument/didChange", params) - def did_close_text_document( - self, params: lsp_types.DidCloseTextDocumentParams - ) -> None: + def did_close_text_document(self, params: lsp_types.DidCloseTextDocumentParams) -> None: """The document close notification is sent from the client to the server when the document got closed in the client. The document's truth now exists where the document's uri points to (e.g. if the document's uri is a file uri the truth now exists on disk). As with the open notification the close notification is about managing the document's content. Receiving a close notification doesn't mean that the document was open in an editor before. A close - notification requires a previous open notification to be sent.""" + notification requires a previous open notification to be sent. + """ return self.send_notification("textDocument/didClose", params) - def did_save_text_document( - self, params: lsp_types.DidSaveTextDocumentParams - ) -> None: + def did_save_text_document(self, params: lsp_types.DidSaveTextDocumentParams) -> None: """The document save notification is sent from the client to the server when - the document got saved in the client.""" + the document got saved in the client. + """ return self.send_notification("textDocument/didSave", params) - def will_save_text_document( - self, params: lsp_types.WillSaveTextDocumentParams - ) -> None: + def will_save_text_document(self, params: lsp_types.WillSaveTextDocumentParams) -> None: """A document will save notification is sent from the client to the server before - the document is actually saved.""" + the document is actually saved. + """ return self.send_notification("textDocument/willSave", params) - def did_change_watched_files( - self, params: lsp_types.DidChangeWatchedFilesParams - ) -> None: + def did_change_watched_files(self, params: lsp_types.DidChangeWatchedFilesParams) -> None: """The watched files notification is sent from the client to the server when - the client detects changes to file watched by the language client.""" + the client detects changes to file watched by the language client. + """ return self.send_notification("workspace/didChangeWatchedFiles", params) def set_trace(self, params: lsp_types.SetTraceParams) -> None: diff --git a/src/multilspy/lsp_protocol_handler/lsp_types.py b/src/solidlsp/lsp_protocol_handler/lsp_types.py similarity index 93% rename from src/multilspy/lsp_protocol_handler/lsp_types.py rename to src/solidlsp/lsp_protocol_handler/lsp_types.py index 6d6a7546..5488c358 100644 --- a/src/multilspy/lsp_protocol_handler/lsp_types.py +++ b/src/solidlsp/lsp_protocol_handler/lsp_types.py @@ -30,8 +30,9 @@ SOFTWARE. """ from enum import Enum, IntEnum, IntFlag -from typing import Dict, List, Literal, Union -from typing_extensions import NotRequired, TypedDict +from typing import Literal, NotRequired, Union + +from typing_extensions import TypedDict URI = str DocumentUri = str @@ -44,7 +45,8 @@ class SemanticTokenTypes(Enum): an clients can specify additional token types via the corresponding client capabilities. - @since 3.16.0""" + @since 3.16.0 + """ Namespace = "namespace" Type = "type" @@ -79,7 +81,8 @@ class SemanticTokenModifiers(Enum): an clients can specify additional token types via the corresponding client capabilities. - @since 3.16.0""" + @since 3.16.0 + """ Declaration = "declaration" Definition = "definition" @@ -96,7 +99,8 @@ class SemanticTokenModifiers(Enum): class DocumentDiagnosticReportKind(Enum): """The document diagnostic report kinds. - @since 3.17.0""" + @since 3.17.0 + """ Full = "full" """ A diagnostic report with a full @@ -191,8 +195,8 @@ class SymbolKind(IntEnum): Event = 24 Operator = 25 TypeParameter = 26 - - @classmethod + + @classmethod def from_int(cls, value: int) -> "SymbolKind": for symbol_kind in cls: if symbol_kind.value == value: @@ -203,7 +207,8 @@ class SymbolKind(IntEnum): class SymbolTag(IntEnum): """Symbol tags are extra annotations that tweak the rendering of a symbol. - @since 3.16""" + @since 3.16 + """ Deprecated = 1 """ Render a symbol as obsolete, usually using a strike-out. """ @@ -212,7 +217,8 @@ class SymbolTag(IntEnum): class UniquenessLevel(Enum): """Moniker uniqueness level to define scope of the moniker. - @since 3.16.0""" + @since 3.16.0 + """ Document = "document" """ The moniker is only unique inside a document """ @@ -229,7 +235,8 @@ class UniquenessLevel(Enum): class MonikerKind(Enum): """The moniker kind. - @since 3.16.0""" + @since 3.16.0 + """ Import = "import" """ The moniker represent a symbol that is imported into a project """ @@ -243,7 +250,8 @@ class MonikerKind(Enum): class InlayHintKind(IntEnum): """Inlay hint kinds. - @since 3.17.0""" + @since 3.17.0 + """ Type = 1 """ An inlay hint that for a type annotation. """ @@ -266,7 +274,8 @@ class MessageType(IntEnum): class TextDocumentSyncKind(IntEnum): """Defines how the host (editor) should sync - document changes to the language server.""" + document changes to the language server. + """ None_ = 0 """ Documents should not be synced at all. """ @@ -325,7 +334,8 @@ class CompletionItemTag(IntEnum): """Completion item tags are extra annotations that tweak the rendering of a completion item. - @since 3.15.0""" + @since 3.15.0 + """ Deprecated = 1 """ Render a completion as obsolete, usually using a strike-out. """ @@ -333,7 +343,8 @@ class CompletionItemTag(IntEnum): class InsertTextFormat(IntEnum): """Defines whether the insert text in a completion item should be interpreted as - plain text or a snippet.""" + plain text or a snippet. + """ PlainText = 1 """ The primary text to be inserted is treated as a plain string. """ @@ -352,7 +363,8 @@ class InsertTextMode(IntEnum): """How whitespace and indentation is handled during completion item insertion. - @since 3.16.0""" + @since 3.16.0 + """ AsIs = 1 """ The insertion or replace strings is taken as it is. If the @@ -449,7 +461,8 @@ class MarkupKind(Enum): result literals like `Hover`, `ParameterInfo` or `CompletionItem`. Please note that `MarkupKinds` must not start with a `$`. This kinds - are reserved for internal usage.""" + are reserved for internal usage. + """ PlainText = "plaintext" """ Plain text is supported as a content format """ @@ -460,7 +473,8 @@ class MarkupKind(Enum): class PositionEncodingKind(Enum): """A set of predefined position encoding kinds. - @since 3.17.0""" + @since 3.17.0 + """ UTF8 = "utf-8" """ Character offsets count UTF-8 code units. """ @@ -513,7 +527,8 @@ class DiagnosticSeverity(IntEnum): class DiagnosticTag(IntEnum): """The diagnostic tags. - @since 3.15.0""" + @since 3.15.0 + """ Unnecessary = 1 """ Unused or unnecessary code. @@ -542,7 +557,8 @@ class CompletionTriggerKind(IntEnum): class SignatureHelpTriggerKind(IntEnum): """How a signature help was triggered. - @since 3.15.0""" + @since 3.15.0 + """ Invoked = 1 """ Signature help was invoked manually by the user or by a command. """ @@ -555,7 +571,8 @@ class SignatureHelpTriggerKind(IntEnum): class CodeActionTriggerKind(IntEnum): """The reason why code actions were requested. - @since 3.17.0""" + @since 3.17.0 + """ Invoked = 1 """ Code actions were explicitly requested by the user or by an extension. """ @@ -570,7 +587,8 @@ class FileOperationPatternKind(Enum): """A pattern kind describing if a glob pattern matches a file a folder or both. - @since 3.16.0""" + @since 3.16.0 + """ File = "file" """ The pattern matches a file only. """ @@ -581,7 +599,8 @@ class FileOperationPatternKind(Enum): class NotebookCellKind(IntEnum): """A notebook cell kind. - @since 3.17.0""" + @since 3.17.0 + """ Markup = 1 """ A markup-cell is formatted source that is used for display. """ @@ -624,7 +643,7 @@ class TokenFormat(Enum): Relative = "relative" -Definition = Union["Location", List["Location"]] +Definition = Union["Location", list["Location"]] """ The definition of a symbol represented as one or many {@link Location locations}. For most programming languages there is only one location at which a symbol is defined. @@ -638,7 +657,7 @@ DefinitionLink = "LocationLink" Provides additional metadata over normal {@link Location location} definitions, including the range of the defining symbol """ -LSPArray = List["LSPAny"] +LSPArray = list["LSPAny"] """ LSP arrays. @since 3.17.0 """ @@ -650,7 +669,7 @@ convenience it is allowed and assumed that all these properties are optional as well. @since 3.17.0 """ -Declaration = Union["Location", List["Location"]] +Declaration = Union["Location", list["Location"]] """ The declaration of a symbol representation as one or many {@link Location locations}. """ DeclarationLink = "LocationLink" @@ -662,9 +681,7 @@ the declaring symbol. Servers should prefer returning `DeclarationLink` over `Declaration` if supported by the client. """ -InlineValue = Union[ - "InlineValueText", "InlineValueVariableLookup", "InlineValueEvaluatableExpression" -] +InlineValue = Union["InlineValueText", "InlineValueVariableLookup", "InlineValueEvaluatableExpression"] """ Inline value information can be provided by different means: - directly as a text value (class InlineValueText). - as a name to use for a variable lookup (class InlineValueVariableLookup) @@ -673,9 +690,7 @@ The InlineValue types combines all inline value types into one type. @since 3.17.0 """ -DocumentDiagnosticReport = Union[ - "RelatedFullDocumentDiagnosticReport", "RelatedUnchangedDocumentDiagnosticReport" -] +DocumentDiagnosticReport = Union["RelatedFullDocumentDiagnosticReport", "RelatedUnchangedDocumentDiagnosticReport"] """ The result of a document diagnostic pull request. A report can either be a full report containing all diagnostics for the requested document or an unchanged report indicating that nothing @@ -684,14 +699,12 @@ pull request. @since 3.17.0 """ -PrepareRenameResult = Union[ - "Range", "__PrepareRenameResult_Type_1", "__PrepareRenameResult_Type_2" -] +PrepareRenameResult = Union["Range", "__PrepareRenameResult_Type_1", "__PrepareRenameResult_Type_2"] -DocumentSelector = List["DocumentFilter"] +DocumentSelector = list["DocumentFilter"] """ A document selector is the combination of one or many document filters. -@sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**∕tsconfig.json' }]`; +@sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**/tsconfig.json' }]`; The use of a string as a document filter is deprecated @since 3.16.0. """ @@ -708,9 +721,7 @@ WorkspaceDocumentDiagnosticReport = Union[ @since 3.17.0 """ -TextDocumentContentChangeEvent = Union[ - "__TextDocumentContentChangeEvent_Type_1", "__TextDocumentContentChangeEvent_Type_2" -] +TextDocumentContentChangeEvent = Union["__TextDocumentContentChangeEvent_Type_1", "__TextDocumentContentChangeEvent_Type_2"] """ An event describing a change to a text document. If only a text is provided it is considered to be the full content of the document. """ @@ -734,7 +745,7 @@ a notebook cell document. @since 3.17.0 - proposed support for NotebookCellTextDocumentFilter. """ -LSPObject = Dict[str, "LSPAny"] +LSPObject = dict[str, "LSPAny"] """ LSP object definition. @since 3.17.0 """ @@ -756,7 +767,7 @@ Glob patterns can have the following syntax: - `*` to match one or more characters in a path segment - `?` to match on one character in a path segment - `**` to match any number of path segments, including none -- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +- `{}` to group sub patterns into an OR expression. (e.g. `**\u200b/*.{ts,js}` matches all TypeScript and JavaScript files) - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) @@ -781,7 +792,7 @@ Pattern = str - `*` to match one or more characters in a path segment - `?` to match on one character in a path segment - `**` to match any number of path segments, including none -- `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +- `{}` to group conditions (e.g. `**\u200b/*.{ts,js}` matches all TypeScript and JavaScript files) - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) @@ -802,7 +813,8 @@ class ImplementationParams(TypedDict): class Location(TypedDict): """Represents a location inside a resource, such as a line - inside a text file.""" + inside a text file. + """ uri: "DocumentUri" range: "Range" @@ -858,7 +870,7 @@ class DidChangeWorkspaceFoldersParams(TypedDict): class ConfigurationParams(TypedDict): """The parameters of a configuration request.""" - items: List["ConfigurationItem"] + items: list["ConfigurationItem"] class DocumentColorParams(TypedDict): @@ -916,7 +928,7 @@ class ColorPresentation(TypedDict): """ An {@link TextEdit edit} which is applied to a document when selecting this presentation for the color. When `falsy` the {@link ColorPresentation.label label} is used. """ - additionalTextEdits: NotRequired[List["TextEdit"]] + additionalTextEdits: NotRequired[list["TextEdit"]] """ An optional array of additional {@link TextEdit text edits} that are applied when selecting this color presentation. Edits must not overlap with the main {@link ColorPresentation.textEdit edit} nor with themselves. """ @@ -1007,7 +1019,7 @@ class SelectionRangeParams(TypedDict): textDocument: "TextDocumentIdentifier" """ The text document. """ - positions: List["Position"] + positions: list["Position"] """ The positions inside the text document. """ workDoneToken: NotRequired["ProgressToken"] """ An optional token that a server can use to report work done progress. """ @@ -1018,7 +1030,8 @@ class SelectionRangeParams(TypedDict): class SelectionRange(TypedDict): """A selection range represents a part of a selection hierarchy. A selection range - may have a parent selection range that contains it.""" + may have a parent selection range that contains it. + """ range: "Range" """ The {@link Range range} of this selection range. """ @@ -1048,7 +1061,8 @@ class WorkDoneProgressCancelParams(TypedDict): class CallHierarchyPrepareParams(TypedDict): """The parameter of a `textDocument/prepareCallHierarchy` request. - @since 3.16.0""" + @since 3.16.0 + """ textDocument: "TextDocumentIdentifier" """ The text document. """ @@ -1062,13 +1076,14 @@ class CallHierarchyItem(TypedDict): """Represents programming constructs like functions or constructors in the context of call hierarchy. - @since 3.16.0""" + @since 3.16.0 + """ name: str """ The name of this item. """ kind: "SymbolKind" """ The kind of this item. """ - tags: NotRequired[List["SymbolTag"]] + tags: NotRequired[list["SymbolTag"]] """ Tags for this item. """ detail: NotRequired[str] """ More detail for this item, e.g. the signature of a function. """ @@ -1087,7 +1102,8 @@ class CallHierarchyItem(TypedDict): class CallHierarchyRegistrationOptions(TypedDict): """Call hierarchy options used during static or dynamic registration. - @since 3.16.0""" + @since 3.16.0 + """ documentSelector: Union["DocumentSelector", None] """ A document selector to identify the scope of the registration. If set to null @@ -1100,7 +1116,8 @@ class CallHierarchyRegistrationOptions(TypedDict): class CallHierarchyIncomingCallsParams(TypedDict): """The parameter of a `callHierarchy/incomingCalls` request. - @since 3.16.0""" + @since 3.16.0 + """ item: "CallHierarchyItem" workDoneToken: NotRequired["ProgressToken"] @@ -1117,7 +1134,7 @@ CallHierarchyIncomingCall = TypedDict( "from": "CallHierarchyItem", # The ranges at which the calls appear. This is relative to the caller # denoted by {@link CallHierarchyIncomingCall.from `this.from`}. - "fromRanges": List["Range"], + "fromRanges": list["Range"], }, ) """ Represents an incoming call, e.g. a caller of a method or constructor. @@ -1128,7 +1145,8 @@ CallHierarchyIncomingCall = TypedDict( class CallHierarchyOutgoingCallsParams(TypedDict): """The parameter of a `callHierarchy/outgoingCalls` request. - @since 3.16.0""" + @since 3.16.0 + """ item: "CallHierarchyItem" workDoneToken: NotRequired["ProgressToken"] @@ -1141,11 +1159,12 @@ class CallHierarchyOutgoingCallsParams(TypedDict): class CallHierarchyOutgoingCall(TypedDict): """Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc. - @since 3.16.0""" + @since 3.16.0 + """ to: "CallHierarchyItem" """ The item that is called. """ - fromRanges: List["Range"] + fromRanges: list["Range"] """ The range at which this item is called. This is the range relative to the caller, e.g the item passed to {@link CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls `provideCallHierarchyOutgoingCalls`} and not {@link CallHierarchyOutgoingCall.to `this.to`}. """ @@ -1171,14 +1190,14 @@ class SemanticTokens(TypedDict): the client will include the result id in the next semantic token request. A server can then instead of computing all semantic tokens again simply send a delta. """ - data: List[Uint] + data: list[Uint] """ The actual tokens. """ class SemanticTokensPartialResult(TypedDict): """@since 3.16.0""" - data: List[Uint] + data: list[Uint] class SemanticTokensRegistrationOptions(TypedDict): @@ -1189,7 +1208,7 @@ class SemanticTokensRegistrationOptions(TypedDict): the document selector provided on the client side will be used. """ legend: "SemanticTokensLegend" """ The legend used by the server """ - range: NotRequired[Union[bool, dict]] + range: NotRequired[bool | dict] """ Server supports providing semantic tokens for a specific range of a document. """ full: NotRequired[Union[bool, "__SemanticTokensOptions_full_Type_1"]] @@ -1218,14 +1237,14 @@ class SemanticTokensDelta(TypedDict): """@since 3.16.0""" resultId: NotRequired[str] - edits: List["SemanticTokensEdit"] + edits: list["SemanticTokensEdit"] """ The semantic token edits to transform a previous result into a new result. """ class SemanticTokensDeltaPartialResult(TypedDict): """@since 3.16.0""" - edits: List["SemanticTokensEdit"] + edits: list["SemanticTokensEdit"] class SemanticTokensRangeParams(TypedDict): @@ -1245,7 +1264,8 @@ class SemanticTokensRangeParams(TypedDict): class ShowDocumentParams(TypedDict): """Params to show a document. - @since 3.16.0""" + @since 3.16.0 + """ uri: "URI" """ The document uri to show. """ @@ -1268,7 +1288,8 @@ class ShowDocumentParams(TypedDict): class ShowDocumentResult(TypedDict): """The result of a showDocument request. - @since 3.16.0""" + @since 3.16.0 + """ success: bool """ A boolean indicating if the show was successful. """ @@ -1286,9 +1307,10 @@ class LinkedEditingRangeParams(TypedDict): class LinkedEditingRanges(TypedDict): """The result of a linked editing range request. - @since 3.16.0""" + @since 3.16.0 + """ - ranges: List["Range"] + ranges: list["Range"] """ A list of ranges that can be edited together. The ranges must have identical length and contain identical text content. The ranges cannot overlap. """ wordPattern: NotRequired[str] @@ -1310,9 +1332,10 @@ class CreateFilesParams(TypedDict): """The parameters sent in notifications/requests for user-initiated creation of files. - @since 3.16.0""" + @since 3.16.0 + """ - files: List["FileCreate"] + files: list["FileCreate"] """ An array of all files/folders created in this operation. """ @@ -1328,13 +1351,12 @@ class WorkspaceEdit(TypedDict): An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will cause failure of the operation. How the client recovers from the failure is described by - the client capability: `workspace.workspaceEdit.failureHandling`""" + the client capability: `workspace.workspaceEdit.failureHandling` + """ - changes: NotRequired[Dict["DocumentUri", List["TextEdit"]]] + changes: NotRequired[dict["DocumentUri", list["TextEdit"]]] """ Holds changes to existing resources. """ - documentChanges: NotRequired[ - List[Union["TextDocumentEdit", "CreateFile", "RenameFile", "DeleteFile"]] - ] + documentChanges: NotRequired[list[Union["TextDocumentEdit", "CreateFile", "RenameFile", "DeleteFile"]]] """ Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes are either an array of `TextDocumentEdit`s to express changes to n different text documents where each text document edit addresses a specific version of a text document. Or it can contain @@ -1345,9 +1367,7 @@ class WorkspaceEdit(TypedDict): If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s using the `changes` property are supported. """ - changeAnnotations: NotRequired[ - Dict["ChangeAnnotationIdentifier", "ChangeAnnotation"] - ] + changeAnnotations: NotRequired[dict["ChangeAnnotationIdentifier", "ChangeAnnotation"]] """ A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and delete file / folder operations. @@ -1359,9 +1379,10 @@ class WorkspaceEdit(TypedDict): class FileOperationRegistrationOptions(TypedDict): """The options to register for file operations. - @since 3.16.0""" + @since 3.16.0 + """ - filters: List["FileOperationFilter"] + filters: list["FileOperationFilter"] """ The actual filters. """ @@ -1369,9 +1390,10 @@ class RenameFilesParams(TypedDict): """The parameters sent in notifications/requests for user-initiated renames of files. - @since 3.16.0""" + @since 3.16.0 + """ - files: List["FileRename"] + files: list["FileRename"] """ An array of all files/folders renamed in this operation. When a folder is renamed, only the folder will be included, and not its children. """ @@ -1380,9 +1402,10 @@ class DeleteFilesParams(TypedDict): """The parameters sent in notifications/requests for user-initiated deletes of files. - @since 3.16.0""" + @since 3.16.0 + """ - files: List["FileDelete"] + files: list["FileDelete"] """ An array of all files/folders deleted in this operation. """ @@ -1401,7 +1424,8 @@ class MonikerParams(TypedDict): class Moniker(TypedDict): """Moniker definition to match LSIF 0.5 moniker definition. - @since 3.16.0""" + @since 3.16.0 + """ scheme: str """ The scheme of the moniker. For example tsc or .Net """ @@ -1423,7 +1447,8 @@ class MonikerRegistrationOptions(TypedDict): class TypeHierarchyPrepareParams(TypedDict): """The parameter of a `textDocument/prepareTypeHierarchy` request. - @since 3.17.0""" + @since 3.17.0 + """ textDocument: "TextDocumentIdentifier" """ The text document. """ @@ -1440,7 +1465,7 @@ class TypeHierarchyItem(TypedDict): """ The name of this item. """ kind: "SymbolKind" """ The kind of this item. """ - tags: NotRequired[List["SymbolTag"]] + tags: NotRequired[list["SymbolTag"]] """ Tags for this item. """ detail: NotRequired[str] """ More detail for this item, e.g. the signature of a function. """ @@ -1463,7 +1488,8 @@ class TypeHierarchyItem(TypedDict): class TypeHierarchyRegistrationOptions(TypedDict): """Type hierarchy options used during static or dynamic registration. - @since 3.17.0""" + @since 3.17.0 + """ documentSelector: Union["DocumentSelector", None] """ A document selector to identify the scope of the registration. If set to null @@ -1476,7 +1502,8 @@ class TypeHierarchyRegistrationOptions(TypedDict): class TypeHierarchySupertypesParams(TypedDict): """The parameter of a `typeHierarchy/supertypes` request. - @since 3.17.0""" + @since 3.17.0 + """ item: "TypeHierarchyItem" workDoneToken: NotRequired["ProgressToken"] @@ -1489,7 +1516,8 @@ class TypeHierarchySupertypesParams(TypedDict): class TypeHierarchySubtypesParams(TypedDict): """The parameter of a `typeHierarchy/subtypes` request. - @since 3.17.0""" + @since 3.17.0 + """ item: "TypeHierarchyItem" workDoneToken: NotRequired["ProgressToken"] @@ -1502,7 +1530,8 @@ class TypeHierarchySubtypesParams(TypedDict): class InlineValueParams(TypedDict): """A parameter literal used in inline value requests. - @since 3.17.0""" + @since 3.17.0 + """ textDocument: "TextDocumentIdentifier" """ The text document. """ @@ -1518,7 +1547,8 @@ class InlineValueParams(TypedDict): class InlineValueRegistrationOptions(TypedDict): """Inline value options used during static or dynamic registration. - @since 3.17.0""" + @since 3.17.0 + """ documentSelector: Union["DocumentSelector", None] """ A document selector to identify the scope of the registration. If set to null @@ -1531,7 +1561,8 @@ class InlineValueRegistrationOptions(TypedDict): class InlayHintParams(TypedDict): """A parameter literal used in inlay hint requests. - @since 3.17.0""" + @since 3.17.0 + """ textDocument: "TextDocumentIdentifier" """ The text document. """ @@ -1544,11 +1575,12 @@ class InlayHintParams(TypedDict): class InlayHint(TypedDict): """Inlay hint information. - @since 3.17.0""" + @since 3.17.0 + """ position: "Position" """ The position of this hint. """ - label: Union[str, List["InlayHintLabelPart"]] + label: str | list["InlayHintLabelPart"] """ The label of this hint. A human readable string or an array of InlayHintLabelPart label parts. @@ -1556,7 +1588,7 @@ class InlayHint(TypedDict): kind: NotRequired["InlayHintKind"] """ The kind of this hint. Can be omitted in which case the client should fall back to a reasonable default. """ - textEdits: NotRequired[List["TextEdit"]] + textEdits: NotRequired[list["TextEdit"]] """ Optional text edits that are performed when accepting this inlay hint. *Note* that edits are expected to change the document so that the inlay @@ -1584,7 +1616,8 @@ class InlayHint(TypedDict): class InlayHintRegistrationOptions(TypedDict): """Inlay hint options used during static or dynamic registration. - @since 3.17.0""" + @since 3.17.0 + """ resolveProvider: NotRequired[bool] """ The server provides support to resolve additional @@ -1600,7 +1633,8 @@ class InlayHintRegistrationOptions(TypedDict): class DocumentDiagnosticParams(TypedDict): """Parameters of the document diagnostic request. - @since 3.17.0""" + @since 3.17.0 + """ textDocument: "TextDocumentIdentifier" """ The text document. """ @@ -1618,9 +1652,10 @@ class DocumentDiagnosticParams(TypedDict): class DocumentDiagnosticReportPartialResult(TypedDict): """A partial result for a document diagnostic report. - @since 3.17.0""" + @since 3.17.0 + """ - relatedDocuments: Dict[ + relatedDocuments: dict[ "DocumentUri", Union["FullDocumentDiagnosticReport", "UnchangedDocumentDiagnosticReport"], ] @@ -1629,7 +1664,8 @@ class DocumentDiagnosticReportPartialResult(TypedDict): class DiagnosticServerCancellationData(TypedDict): """Cancellation data returned from a diagnostic request. - @since 3.17.0""" + @since 3.17.0 + """ retriggerRequest: bool @@ -1637,7 +1673,8 @@ class DiagnosticServerCancellationData(TypedDict): class DiagnosticRegistrationOptions(TypedDict): """Diagnostic registration options. - @since 3.17.0""" + @since 3.17.0 + """ documentSelector: Union["DocumentSelector", None] """ A document selector to identify the scope of the registration. If set to null @@ -1660,11 +1697,12 @@ class DiagnosticRegistrationOptions(TypedDict): class WorkspaceDiagnosticParams(TypedDict): """Parameters of the workspace diagnostic request. - @since 3.17.0""" + @since 3.17.0 + """ identifier: NotRequired[str] """ The additional identifier provided during registration. """ - previousResultIds: List["PreviousResultId"] + previousResultIds: list["PreviousResultId"] """ The currently known diagnostic reports with their previous result ids. """ workDoneToken: NotRequired["ProgressToken"] @@ -1677,27 +1715,30 @@ class WorkspaceDiagnosticParams(TypedDict): class WorkspaceDiagnosticReport(TypedDict): """A workspace diagnostic report. - @since 3.17.0""" + @since 3.17.0 + """ - items: List["WorkspaceDocumentDiagnosticReport"] + items: list["WorkspaceDocumentDiagnosticReport"] class WorkspaceDiagnosticReportPartialResult(TypedDict): """A partial result for a workspace diagnostic report. - @since 3.17.0""" + @since 3.17.0 + """ - items: List["WorkspaceDocumentDiagnosticReport"] + items: list["WorkspaceDocumentDiagnosticReport"] class DidOpenNotebookDocumentParams(TypedDict): """The params sent in an open notebook document notification. - @since 3.17.0""" + @since 3.17.0 + """ notebookDocument: "NotebookDocument" """ The notebook document that got opened. """ - cellTextDocuments: List["TextDocumentItem"] + cellTextDocuments: list["TextDocumentItem"] """ The text documents that represent the content of a notebook cell. """ @@ -1705,7 +1746,8 @@ class DidOpenNotebookDocumentParams(TypedDict): class DidChangeNotebookDocumentParams(TypedDict): """The params sent in a change notebook document notification. - @since 3.17.0""" + @since 3.17.0 + """ notebookDocument: "VersionedNotebookDocumentIdentifier" """ The notebook document that did change. The version number points @@ -1731,7 +1773,8 @@ class DidChangeNotebookDocumentParams(TypedDict): class DidSaveNotebookDocumentParams(TypedDict): """The params sent in a save notebook document notification. - @since 3.17.0""" + @since 3.17.0 + """ notebookDocument: "NotebookDocumentIdentifier" """ The notebook document that got saved. """ @@ -1740,25 +1783,26 @@ class DidSaveNotebookDocumentParams(TypedDict): class DidCloseNotebookDocumentParams(TypedDict): """The params sent in a close notebook document notification. - @since 3.17.0""" + @since 3.17.0 + """ notebookDocument: "NotebookDocumentIdentifier" """ The notebook document that got closed. """ - cellTextDocuments: List["TextDocumentIdentifier"] + cellTextDocuments: list["TextDocumentIdentifier"] """ The text documents that represent the content of a notebook cell that got closed. """ class RegistrationParams(TypedDict): - registrations: List["Registration"] + registrations: list["Registration"] class UnregistrationParams(TypedDict): - unregisterations: List["Unregistration"] + unregisterations: list["Unregistration"] class InitializeParams(TypedDict): - processId: Union[int, None] + processId: int | None """ The process Id of the parent process that started the server. @@ -1777,7 +1821,7 @@ class InitializeParams(TypedDict): (See https://en.wikipedia.org/wiki/IETF_language_tag) @since 3.16.0 """ - rootPath: NotRequired[Union[str, None]] + rootPath: NotRequired[str | None] """ The rootPath of the workspace. Is null if no folder is open. @@ -1794,7 +1838,7 @@ class InitializeParams(TypedDict): """ User provided initialization options. """ trace: NotRequired["TraceValues"] """ The initial trace setting. If omitted trace is disabled ('off'). """ - workspaceFolders: NotRequired[Union[List["WorkspaceFolder"], None]] + workspaceFolders: NotRequired[list["WorkspaceFolder"] | None] """ The workspace folders configured in the client when the server starts. This property is only available if the client supports workspace folders. @@ -1817,7 +1861,8 @@ class InitializeResult(TypedDict): class InitializeError(TypedDict): """The data type of the ResponseError if the - initialize request fails.""" + initialize request fails. + """ retry: bool """ Indicates whether the client execute the following retry logic: @@ -1838,7 +1883,7 @@ class DidChangeConfigurationParams(TypedDict): class DidChangeConfigurationRegistrationOptions(TypedDict): - section: NotRequired[Union[str, List[str]]] + section: NotRequired[str | list[str]] class ShowMessageParams(TypedDict): @@ -1855,7 +1900,7 @@ class ShowMessageRequestParams(TypedDict): """ The message type. See {@link MessageType} """ message: str """ The actual message. """ - actions: NotRequired[List["MessageActionItem"]] + actions: NotRequired[list["MessageActionItem"]] """ The message action items to present. """ @@ -1887,7 +1932,7 @@ class DidChangeTextDocumentParams(TypedDict): """ The document that did change. The version number points to the version after all provided content changes have been applied. """ - contentChanges: List["TextDocumentContentChangeEvent"] + contentChanges: list["TextDocumentContentChangeEvent"] """ The actual content changes. The content changes describe single state changes to the document. So if there are two content changes c1 (at array index 0) and c2 (at array index 1) for a document in state S then c1 moves the document from @@ -1961,14 +2006,14 @@ class TextEdit(TypedDict): class DidChangeWatchedFilesParams(TypedDict): """The watched files change notification's parameters.""" - changes: List["FileEvent"] + changes: list["FileEvent"] """ The actual file events. """ class DidChangeWatchedFilesRegistrationOptions(TypedDict): """Describe options to be used when registered for text document change events.""" - watchers: List["FileSystemWatcher"] + watchers: list["FileSystemWatcher"] """ The watchers to register. """ @@ -1981,7 +2026,7 @@ class PublishDiagnosticsParams(TypedDict): """ Optional the version number of the document the diagnostics are published for. @since 3.15.0 """ - diagnostics: List["Diagnostic"] + diagnostics: list["Diagnostic"] """ An array of diagnostic information items. """ @@ -2004,7 +2049,8 @@ class CompletionParams(TypedDict): class CompletionItem(TypedDict): """A completion item represents a text snippet that is - proposed to complete text that is being typed.""" + proposed to complete text that is being typed. + """ label: str """ The label of this completion item. @@ -2021,7 +2067,7 @@ class CompletionItem(TypedDict): kind: NotRequired["CompletionItemKind"] """ The kind of this completion item. Based of the kind an icon is chosen by the editor. """ - tags: NotRequired[List["CompletionItemTag"]] + tags: NotRequired[list["CompletionItemTag"]] """ Tags for this completion item. @since 3.15.0 """ @@ -2104,7 +2150,7 @@ class CompletionItem(TypedDict): property is used as a text. @since 3.17.0 """ - additionalTextEdits: NotRequired[List["TextEdit"]] + additionalTextEdits: NotRequired[list["TextEdit"]] """ An optional array of additional {@link TextEdit text edits} that are applied when selecting this completion. Edits must not overlap (including the same insert position) with the main {@link CompletionItem.textEdit edit} nor with themselves. @@ -2112,7 +2158,7 @@ class CompletionItem(TypedDict): Additional text edits should be used to change text unrelated to the current cursor position (for example adding an import statement at the top of the file if the completion item will insert an unqualified type). """ - commitCharacters: NotRequired[List[str]] + commitCharacters: NotRequired[list[str]] """ An optional set of characters that when pressed while this completion is active will accept it first and then type that character. *Note* that all commit characters should have `length=1` and that superfluous characters will be ignored. """ @@ -2127,7 +2173,8 @@ class CompletionItem(TypedDict): class CompletionList(TypedDict): """Represents a collection of {@link CompletionItem completion items} to be presented - in the editor.""" + in the editor. + """ isIncomplete: bool """ This list it not complete. Further typing results in recomputing this list. @@ -2148,7 +2195,7 @@ class CompletionList(TypedDict): capability. @since 3.17.0 """ - items: List["CompletionItem"] + items: list["CompletionItem"] """ The completion items. """ @@ -2158,7 +2205,7 @@ class CompletionRegistrationOptions(TypedDict): documentSelector: Union["DocumentSelector", None] """ A document selector to identify the scope of the registration. If set to null the document selector provided on the client side will be used. """ - triggerCharacters: NotRequired[List[str]] + triggerCharacters: NotRequired[list[str]] """ Most tools trigger completion request automatically without explicitly requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user starts to type an identifier. For example if the user types `c` in a JavaScript file @@ -2167,7 +2214,7 @@ class CompletionRegistrationOptions(TypedDict): If code complete should automatically be trigger on characters not being valid inside an identifier (for example `.` in JavaScript) list them in `triggerCharacters`. """ - allCommitCharacters: NotRequired[List[str]] + allCommitCharacters: NotRequired[list[str]] """ The list of all possible characters that commit a completion. This field can be used if clients don't support individual commit characters per completion item. See `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport` @@ -2200,7 +2247,7 @@ class HoverParams(TypedDict): class Hover(TypedDict): """The result of a hover request.""" - contents: Union["MarkupContent", "MarkedString", List["MarkedString"]] + contents: Union["MarkupContent", "MarkedString", list["MarkedString"]] """ The hover's content """ range: NotRequired["Range"] """ An optional range inside the text document that is used to @@ -2234,9 +2281,10 @@ class SignatureHelpParams(TypedDict): class SignatureHelp(TypedDict): """Signature help represents the signature of something callable. There can be multiple signature but only one - active and only one active parameter.""" + active and only one active parameter. + """ - signatures: List["SignatureInformation"] + signatures: list["SignatureInformation"] """ One or more signatures. """ activeSignature: NotRequired[Uint] """ The active signature. If omitted or the value lies outside the @@ -2264,9 +2312,9 @@ class SignatureHelpRegistrationOptions(TypedDict): documentSelector: Union["DocumentSelector", None] """ A document selector to identify the scope of the registration. If set to null the document selector provided on the client side will be used. """ - triggerCharacters: NotRequired[List[str]] + triggerCharacters: NotRequired[list[str]] """ List of characters that trigger signature help automatically. """ - retriggerCharacters: NotRequired[List[str]] + retriggerCharacters: NotRequired[list[str]] """ List of characters that re-trigger signature help. These trigger characters are only active when signature help is already showing. All trigger characters @@ -2337,7 +2385,8 @@ class DocumentHighlightParams(TypedDict): class DocumentHighlight(TypedDict): """A document highlight is a range inside a text document which deserves special attention. Usually a document highlight is visualized by changing - the background color of its range.""" + the background color of its range. + """ range: "Range" """ The range this highlight applies to. """ @@ -2367,7 +2416,8 @@ class DocumentSymbolParams(TypedDict): class SymbolInformation(TypedDict): """Represents information about programming constructs like variables, classes, - interfaces etc.""" + interfaces etc. + """ deprecated: NotRequired[bool] """ Indicates if this symbol is deprecated. @@ -2387,7 +2437,7 @@ class SymbolInformation(TypedDict): """ The name of this symbol. """ kind: "SymbolKind" """ The kind of this symbol. """ - tags: NotRequired[List["SymbolTag"]] + tags: NotRequired[list["SymbolTag"]] """ Tags for this symbol. @since 3.16.0 """ @@ -2402,7 +2452,8 @@ class DocumentSymbol(TypedDict): """Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be hierarchical and they have two ranges: one that encloses its definition and one that points to - its most interesting range, e.g. the range of an identifier.""" + its most interesting range, e.g. the range of an identifier. + """ name: str """ The name of this symbol. Will be displayed in the user interface and therefore must not be @@ -2411,7 +2462,7 @@ class DocumentSymbol(TypedDict): """ More detail for this symbol, e.g the signature of a function. """ kind: "SymbolKind" """ The kind of this symbol. """ - tags: NotRequired[List["SymbolTag"]] + tags: NotRequired[list["SymbolTag"]] """ Tags for this document symbol. @since 3.16.0 """ @@ -2461,13 +2512,14 @@ class Command(TypedDict): """Represents a reference to a command. Provides a title which will be used to represent a command in the UI and, optionally, an array of arguments which will be passed to the command handler - function when invoked.""" + function when invoked. + """ title: str """ Title of the command, like `save`. """ command: str """ The identifier of the actual command handler. """ - arguments: NotRequired[List["LSPAny"]] + arguments: NotRequired[list["LSPAny"]] """ Arguments that the command handler should be invoked with. """ @@ -2485,7 +2537,7 @@ class CodeAction(TypedDict): """ The kind of the code action. Used to filter code actions. """ - diagnostics: NotRequired[List["Diagnostic"]] + diagnostics: NotRequired[list["Diagnostic"]] """ The diagnostics that this code action resolves. """ isPreferred: NotRequired[bool] """ Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted @@ -2530,7 +2582,7 @@ class CodeActionRegistrationOptions(TypedDict): documentSelector: Union["DocumentSelector", None] """ A document selector to identify the scope of the registration. If set to null the document selector provided on the client side will be used. """ - codeActionKinds: NotRequired[List["CodeActionKind"]] + codeActionKinds: NotRequired[list["CodeActionKind"]] """ CodeActionKinds that this server may return. The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server @@ -2560,7 +2612,8 @@ class WorkspaceSymbol(TypedDict): See also SymbolInformation. - @since 3.17.0""" + @since 3.17.0 + """ location: Union["Location", "__WorkspaceSymbol_location_Type_1"] """ The location of the symbol. Whether a server is allowed to @@ -2575,7 +2628,7 @@ class WorkspaceSymbol(TypedDict): """ The name of this symbol. """ kind: "SymbolKind" """ The kind of this symbol. """ - tags: NotRequired[List["SymbolTag"]] + tags: NotRequired[list["SymbolTag"]] """ Tags for this symbol. @since 3.16.0 """ @@ -2613,7 +2666,8 @@ class CodeLens(TypedDict): source text, like the number of references, a way to run tests, etc. A code lens is _unresolved_ when no command is associated to it. For performance - reasons the creation of a code lens and resolving should be done in two stages.""" + reasons the creation of a code lens and resolving should be done in two stages. + """ range: "Range" """ The range in which this code lens is valid. Should only span a single line. """ @@ -2649,7 +2703,8 @@ class DocumentLinkParams(TypedDict): class DocumentLink(TypedDict): """A document link is a range in a text document that links to an internal or external resource, like another - text document or a web site.""" + text document or a web site. + """ range: "Range" """ The range this link applies to. """ @@ -2744,7 +2799,7 @@ class DocumentOnTypeFormattingRegistrationOptions(TypedDict): the document selector provided on the client side will be used. """ firstTriggerCharacter: str """ A character on which formatting should be triggered, like `{`. """ - moreTriggerCharacter: NotRequired[List[str]] + moreTriggerCharacter: NotRequired[list[str]] """ More trigger characters. """ @@ -2789,7 +2844,7 @@ class ExecuteCommandParams(TypedDict): command: str """ The identifier of the actual command handler. """ - arguments: NotRequired[List["LSPAny"]] + arguments: NotRequired[list["LSPAny"]] """ Arguments that the command should be invoked with. """ workDoneToken: NotRequired["ProgressToken"] """ An optional token that a server can use to report work done progress. """ @@ -2798,7 +2853,7 @@ class ExecuteCommandParams(TypedDict): class ExecuteCommandRegistrationOptions(TypedDict): """Registration options for a {@link ExecuteCommandRequest}.""" - commands: List[str] + commands: list[str] """ The commands to be executed on the server """ @@ -2816,7 +2871,8 @@ class ApplyWorkspaceEditParams(TypedDict): class ApplyWorkspaceEditResult(TypedDict): """The result returned from the apply workspace edit request. - @since 3.17 renamed from ApplyWorkspaceEditResponse""" + @since 3.17 renamed from ApplyWorkspaceEditResponse + """ applied: bool """ Indicates whether the edit was applied or not. """ @@ -2895,7 +2951,7 @@ class LogTraceParams(TypedDict): class CancelParams(TypedDict): - id: Union[int, str] + id: int | str """ The request id to cancel. """ @@ -2908,7 +2964,8 @@ class ProgressParams(TypedDict): class TextDocumentPositionParams(TypedDict): """A parameter literal used in requests to pass a text document and a position inside that - document.""" + document. + """ textDocument: "TextDocumentIdentifier" """ The text document. """ @@ -2929,7 +2986,8 @@ class PartialResultParams(TypedDict): class LocationLink(TypedDict): """Represents the connection of two locations. Provides additional metadata over normal {@link Location locations}, - including an origin range.""" + including an origin range. + """ originSelectionRange: NotRequired["Range"] """ Span of the origin of this link. @@ -2958,7 +3016,8 @@ class Range(TypedDict): start: { line: 5, character: 23 } end : { line 6, character : 0 } } - ```""" + ``` + """ start: "Position" """ The range's start position. """ @@ -2972,7 +3031,8 @@ class ImplementationOptions(TypedDict): class StaticRegistrationOptions(TypedDict): """Static registration options to be returned in the initialize - request.""" + request. + """ id: NotRequired[str] """ The id used to register the request. The id can be used to deregister @@ -2986,9 +3046,9 @@ class TypeDefinitionOptions(TypedDict): class WorkspaceFoldersChangeEvent(TypedDict): """The workspace folder change event.""" - added: List["WorkspaceFolder"] + added: list["WorkspaceFolder"] """ The array of added workspace folders """ - removed: List["WorkspaceFolder"] + removed: list["WorkspaceFolder"] """ The array of the removed workspace folders """ @@ -3032,7 +3092,7 @@ class DeclarationOptions(TypedDict): class Position(TypedDict): - """Position in a text document expressed as zero-based line and character + r"""Position in a text document expressed as zero-based line and character offset. Prior to 3.17 the offsets were always based on a UTF-16 string representation. So a string of the form `a𐐀b` the character offset of the character `a` is 0, the character offset of `𐐀` is 1 and the character @@ -3058,7 +3118,8 @@ class Position(TypedDict): Positions are line end character agnostic. So you can not specify a position that denotes `\r|\n` or `\n|` where `|` represents the character offset. - @since 3.17.0 - support for negotiated position encoding.""" + @since 3.17.0 - support for negotiated position encoding. + """ line: Uint """ Line position in a document (zero-based). @@ -3082,7 +3143,8 @@ class SelectionRangeOptions(TypedDict): class CallHierarchyOptions(TypedDict): """Call hierarchy options used during static registration. - @since 3.16.0""" + @since 3.16.0 + """ workDoneProgress: NotRequired[bool] @@ -3092,7 +3154,7 @@ class SemanticTokensOptions(TypedDict): legend: "SemanticTokensLegend" """ The legend used by the server """ - range: NotRequired[Union[bool, dict]] + range: NotRequired[bool | dict] """ Server supports providing semantic tokens for a specific range of a document. """ full: NotRequired[Union[bool, "__SemanticTokensOptions_full_Type_2"]] @@ -3107,7 +3169,7 @@ class SemanticTokensEdit(TypedDict): """ The start offset of the edit. """ deleteCount: Uint """ The count of elements to remove. """ - data: NotRequired[List[Uint]] + data: NotRequired[list[Uint]] """ The elements to insert. """ @@ -3118,7 +3180,8 @@ class LinkedEditingRangeOptions(TypedDict): class FileCreate(TypedDict): """Represents information on a file/folder create. - @since 3.16.0""" + @since 3.16.0 + """ uri: str """ A file:// URI for the location of the file/folder being created. """ @@ -3128,11 +3191,12 @@ class TextDocumentEdit(TypedDict): """Describes textual changes on a text document. A TextDocumentEdit describes all changes on a document version Si and after they are applied move the document to version Si+1. So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any - kind of ordering. However the edits must be non overlapping.""" + kind of ordering. However the edits must be non overlapping. + """ textDocument: "OptionalVersionedTextDocumentIdentifier" """ The text document to change. """ - edits: List[Union["TextEdit", "AnnotatedTextEdit"]] + edits: list[Union["TextEdit", "AnnotatedTextEdit"]] """ The edits to be applied. @since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a @@ -3189,7 +3253,8 @@ class DeleteFile(TypedDict): class ChangeAnnotation(TypedDict): """Additional information that describes document changes. - @since 3.16.0""" + @since 3.16.0 + """ label: str """ A human-readable string describing the actual change. The string @@ -3206,7 +3271,8 @@ class FileOperationFilter(TypedDict): """A filter to describe in which file operation requests or notifications the server is interested in receiving. - @since 3.16.0""" + @since 3.16.0 + """ scheme: NotRequired[str] """ A Uri scheme like `file` or `untitled`. """ @@ -3217,7 +3283,8 @@ class FileOperationFilter(TypedDict): class FileRename(TypedDict): """Represents information on a file/folder rename. - @since 3.16.0""" + @since 3.16.0 + """ oldUri: str """ A file:// URI for the original location of the file/folder being renamed. """ @@ -3228,7 +3295,8 @@ class FileRename(TypedDict): class FileDelete(TypedDict): """Represents information on a file/folder delete. - @since 3.16.0""" + @since 3.16.0 + """ uri: str """ A file:// URI for the location of the file/folder being deleted. """ @@ -3241,7 +3309,8 @@ class MonikerOptions(TypedDict): class TypeHierarchyOptions(TypedDict): """Type hierarchy options used during static registration. - @since 3.17.0""" + @since 3.17.0 + """ workDoneProgress: NotRequired[bool] @@ -3259,7 +3328,8 @@ class InlineValueContext(TypedDict): class InlineValueText(TypedDict): """Provide inline value as text. - @since 3.17.0""" + @since 3.17.0 + """ range: "Range" """ The document range for which the inline value applies. """ @@ -3272,7 +3342,8 @@ class InlineValueVariableLookup(TypedDict): If only a range is specified, the variable name will be extracted from the underlying document. An optional variable name can be used to override the extracted name. - @since 3.17.0""" + @since 3.17.0 + """ range: "Range" """ The document range for which the inline value applies. @@ -3288,7 +3359,8 @@ class InlineValueEvaluatableExpression(TypedDict): If only a range is specified, the expression will be extracted from the underlying document. An optional expression can be used to override the extracted expression. - @since 3.17.0""" + @since 3.17.0 + """ range: "Range" """ The document range for which the inline value applies. @@ -3300,7 +3372,8 @@ class InlineValueEvaluatableExpression(TypedDict): class InlineValueOptions(TypedDict): """Inline value options used during static registration. - @since 3.17.0""" + @since 3.17.0 + """ workDoneProgress: NotRequired[bool] @@ -3309,7 +3382,8 @@ class InlayHintLabelPart(TypedDict): """An inlay hint label part allows for interactive and composite labels of inlay hints. - @since 3.17.0""" + @since 3.17.0 + """ value: str """ The value of this label part. """ @@ -3337,7 +3411,7 @@ class InlayHintLabelPart(TypedDict): class MarkupContent(TypedDict): - """A `MarkupContent` literal represents a string value which content is interpreted base on its + r"""A `MarkupContent` literal represents a string value which content is interpreted base on its kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. @@ -3358,7 +3432,8 @@ class MarkupContent(TypedDict): ``` *Please Note* that clients might sanitize the return markdown. A client could decide to - remove HTML from the markdown to avoid script execution.""" + remove HTML from the markdown to avoid script execution. + """ kind: "MarkupKind" """ The type of the Markup """ @@ -3369,7 +3444,8 @@ class MarkupContent(TypedDict): class InlayHintOptions(TypedDict): """Inlay hint options used during static registration. - @since 3.17.0""" + @since 3.17.0 + """ resolveProvider: NotRequired[bool] """ The server provides support to resolve additional @@ -3380,10 +3456,11 @@ class InlayHintOptions(TypedDict): class RelatedFullDocumentDiagnosticReport(TypedDict): """A full diagnostic report with a set of related documents. - @since 3.17.0""" + @since 3.17.0 + """ relatedDocuments: NotRequired[ - Dict[ + dict[ "DocumentUri", Union["FullDocumentDiagnosticReport", "UnchangedDocumentDiagnosticReport"], ] @@ -3401,17 +3478,18 @@ class RelatedFullDocumentDiagnosticReport(TypedDict): """ An optional result id. If provided it will be sent on the next diagnostic request for the same document. """ - items: List["Diagnostic"] + items: list["Diagnostic"] """ The actual items. """ class RelatedUnchangedDocumentDiagnosticReport(TypedDict): """An unchanged diagnostic report with a set of related documents. - @since 3.17.0""" + @since 3.17.0 + """ relatedDocuments: NotRequired[ - Dict[ + dict[ "DocumentUri", Union["FullDocumentDiagnosticReport", "UnchangedDocumentDiagnosticReport"], ] @@ -3436,7 +3514,8 @@ class RelatedUnchangedDocumentDiagnosticReport(TypedDict): class FullDocumentDiagnosticReport(TypedDict): """A diagnostic report with a full set of problems. - @since 3.17.0""" + @since 3.17.0 + """ kind: Literal["full"] """ A full document diagnostic report. """ @@ -3444,7 +3523,7 @@ class FullDocumentDiagnosticReport(TypedDict): """ An optional result id. If provided it will be sent on the next diagnostic request for the same document. """ - items: List["Diagnostic"] + items: list["Diagnostic"] """ The actual items. """ @@ -3452,7 +3531,8 @@ class UnchangedDocumentDiagnosticReport(TypedDict): """A diagnostic report indicating that the last returned report is still accurate. - @since 3.17.0""" + @since 3.17.0 + """ kind: Literal["unchanged"] """ A document diagnostic report indicating @@ -3467,7 +3547,8 @@ class UnchangedDocumentDiagnosticReport(TypedDict): class DiagnosticOptions(TypedDict): """Diagnostic options. - @since 3.17.0""" + @since 3.17.0 + """ identifier: NotRequired[str] """ An optional identifier under which the diagnostics are @@ -3485,7 +3566,8 @@ class DiagnosticOptions(TypedDict): class PreviousResultId(TypedDict): """A previous result id in a workspace pull request. - @since 3.17.0""" + @since 3.17.0 + """ uri: "DocumentUri" """ The URI for which the client knowns a @@ -3497,7 +3579,8 @@ class PreviousResultId(TypedDict): class NotebookDocument(TypedDict): """A notebook document. - @since 3.17.0""" + @since 3.17.0 + """ uri: "URI" """ The notebook document's uri. """ @@ -3511,13 +3594,14 @@ class NotebookDocument(TypedDict): document. Note: should always be an object literal (e.g. LSPObject) """ - cells: List["NotebookCell"] + cells: list["NotebookCell"] """ The cells of a notebook. """ class TextDocumentItem(TypedDict): """An item to transfer a text document from the client to the - server.""" + server. + """ uri: "DocumentUri" """ The text document's uri. """ @@ -3533,7 +3617,8 @@ class TextDocumentItem(TypedDict): class VersionedNotebookDocumentIdentifier(TypedDict): """A versioned notebook document identifier. - @since 3.17.0""" + @since 3.17.0 + """ version: int """ The version number of this notebook document. """ @@ -3544,7 +3629,8 @@ class VersionedNotebookDocumentIdentifier(TypedDict): class NotebookDocumentChangeEvent(TypedDict): """A change event for a notebook document. - @since 3.17.0""" + @since 3.17.0 + """ metadata: NotRequired["LSPObject"] """ The changed meta data if any. @@ -3557,7 +3643,8 @@ class NotebookDocumentChangeEvent(TypedDict): class NotebookDocumentIdentifier(TypedDict): """A literal to identify a notebook document in the client. - @since 3.17.0""" + @since 3.17.0 + """ uri: "URI" """ The notebook document's uri. """ @@ -3586,7 +3673,7 @@ class Unregistration(TypedDict): class WorkspaceFoldersInitializeParams(TypedDict): - workspaceFolders: NotRequired[Union[List["WorkspaceFolder"], None]] + workspaceFolders: NotRequired[list["WorkspaceFolder"] | None] """ The workspace folders configured in the client when the server starts. This property is only available if the client supports workspace folders. @@ -3598,7 +3685,8 @@ class WorkspaceFoldersInitializeParams(TypedDict): class ServerCapabilities(TypedDict): """Defines the capabilities provided by a language - server.""" + server. + """ positionEncoding: NotRequired["PositionEncodingKind"] """ The position encoding the server picked from the encodings offered @@ -3610,15 +3698,11 @@ class ServerCapabilities(TypedDict): If omitted it defaults to 'utf-16'. @since 3.17.0 """ - textDocumentSync: NotRequired[ - Union["TextDocumentSyncOptions", "TextDocumentSyncKind"] - ] + textDocumentSync: NotRequired[Union["TextDocumentSyncOptions", "TextDocumentSyncKind"]] """ Defines how text documents are synced. Is either a detailed structure defining each notification or for backwards compatibility the TextDocumentSyncKind number. """ - notebookDocumentSync: NotRequired[ - Union["NotebookDocumentSyncOptions", "NotebookDocumentSyncRegistrationOptions"] - ] + notebookDocumentSync: NotRequired[Union["NotebookDocumentSyncOptions", "NotebookDocumentSyncRegistrationOptions"]] """ Defines how notebook documents are synced. @since 3.17.0 """ @@ -3628,19 +3712,13 @@ class ServerCapabilities(TypedDict): """ The server provides hover support. """ signatureHelpProvider: NotRequired["SignatureHelpOptions"] """ The server provides signature help support. """ - declarationProvider: NotRequired[ - Union[bool, "DeclarationOptions", "DeclarationRegistrationOptions"] - ] + declarationProvider: NotRequired[Union[bool, "DeclarationOptions", "DeclarationRegistrationOptions"]] """ The server provides Goto Declaration support. """ definitionProvider: NotRequired[Union[bool, "DefinitionOptions"]] """ The server provides goto definition support. """ - typeDefinitionProvider: NotRequired[ - Union[bool, "TypeDefinitionOptions", "TypeDefinitionRegistrationOptions"] - ] + typeDefinitionProvider: NotRequired[Union[bool, "TypeDefinitionOptions", "TypeDefinitionRegistrationOptions"]] """ The server provides Goto Type Definition support. """ - implementationProvider: NotRequired[ - Union[bool, "ImplementationOptions", "ImplementationRegistrationOptions"] - ] + implementationProvider: NotRequired[Union[bool, "ImplementationOptions", "ImplementationRegistrationOptions"]] """ The server provides Goto Implementation support. """ referencesProvider: NotRequired[Union[bool, "ReferenceOptions"]] """ The server provides find references support. """ @@ -3656,17 +3734,13 @@ class ServerCapabilities(TypedDict): """ The server provides code lens. """ documentLinkProvider: NotRequired["DocumentLinkOptions"] """ The server provides document link support. """ - colorProvider: NotRequired[ - Union[bool, "DocumentColorOptions", "DocumentColorRegistrationOptions"] - ] + colorProvider: NotRequired[Union[bool, "DocumentColorOptions", "DocumentColorRegistrationOptions"]] """ The server provides color provider support. """ workspaceSymbolProvider: NotRequired[Union[bool, "WorkspaceSymbolOptions"]] """ The server provides workspace symbol support. """ documentFormattingProvider: NotRequired[Union[bool, "DocumentFormattingOptions"]] """ The server provides document formatting. """ - documentRangeFormattingProvider: NotRequired[ - Union[bool, "DocumentRangeFormattingOptions"] - ] + documentRangeFormattingProvider: NotRequired[Union[bool, "DocumentRangeFormattingOptions"]] """ The server provides document range formatting. """ documentOnTypeFormattingProvider: NotRequired["DocumentOnTypeFormattingOptions"] """ The server provides document formatting on typing. """ @@ -3674,63 +3748,41 @@ class ServerCapabilities(TypedDict): """ The server provides rename support. RenameOptions may only be specified if the client states that it supports `prepareSupport` in its initial `initialize` request. """ - foldingRangeProvider: NotRequired[ - Union[bool, "FoldingRangeOptions", "FoldingRangeRegistrationOptions"] - ] + foldingRangeProvider: NotRequired[Union[bool, "FoldingRangeOptions", "FoldingRangeRegistrationOptions"]] """ The server provides folding provider support. """ - selectionRangeProvider: NotRequired[ - Union[bool, "SelectionRangeOptions", "SelectionRangeRegistrationOptions"] - ] + selectionRangeProvider: NotRequired[Union[bool, "SelectionRangeOptions", "SelectionRangeRegistrationOptions"]] """ The server provides selection range support. """ executeCommandProvider: NotRequired["ExecuteCommandOptions"] """ The server provides execute command support. """ - callHierarchyProvider: NotRequired[ - Union[bool, "CallHierarchyOptions", "CallHierarchyRegistrationOptions"] - ] + callHierarchyProvider: NotRequired[Union[bool, "CallHierarchyOptions", "CallHierarchyRegistrationOptions"]] """ The server provides call hierarchy support. @since 3.16.0 """ - linkedEditingRangeProvider: NotRequired[ - Union[ - bool, "LinkedEditingRangeOptions", "LinkedEditingRangeRegistrationOptions" - ] - ] + linkedEditingRangeProvider: NotRequired[Union[bool, "LinkedEditingRangeOptions", "LinkedEditingRangeRegistrationOptions"]] """ The server provides linked editing range support. @since 3.16.0 """ - semanticTokensProvider: NotRequired[ - Union["SemanticTokensOptions", "SemanticTokensRegistrationOptions"] - ] + semanticTokensProvider: NotRequired[Union["SemanticTokensOptions", "SemanticTokensRegistrationOptions"]] """ The server provides semantic tokens support. @since 3.16.0 """ - monikerProvider: NotRequired[ - Union[bool, "MonikerOptions", "MonikerRegistrationOptions"] - ] + monikerProvider: NotRequired[Union[bool, "MonikerOptions", "MonikerRegistrationOptions"]] """ The server provides moniker support. @since 3.16.0 """ - typeHierarchyProvider: NotRequired[ - Union[bool, "TypeHierarchyOptions", "TypeHierarchyRegistrationOptions"] - ] + typeHierarchyProvider: NotRequired[Union[bool, "TypeHierarchyOptions", "TypeHierarchyRegistrationOptions"]] """ The server provides type hierarchy support. @since 3.17.0 """ - inlineValueProvider: NotRequired[ - Union[bool, "InlineValueOptions", "InlineValueRegistrationOptions"] - ] + inlineValueProvider: NotRequired[Union[bool, "InlineValueOptions", "InlineValueRegistrationOptions"]] """ The server provides inline values. @since 3.17.0 """ - inlayHintProvider: NotRequired[ - Union[bool, "InlayHintOptions", "InlayHintRegistrationOptions"] - ] + inlayHintProvider: NotRequired[Union[bool, "InlayHintOptions", "InlayHintRegistrationOptions"]] """ The server provides inlay hints. @since 3.17.0 """ - diagnosticProvider: NotRequired[ - Union["DiagnosticOptions", "DiagnosticRegistrationOptions"] - ] + diagnosticProvider: NotRequired[Union["DiagnosticOptions", "DiagnosticRegistrationOptions"]] """ The server has support for pull model diagnostics. @since 3.17.0 """ @@ -3778,14 +3830,15 @@ class FileSystemWatcher(TypedDict): class Diagnostic(TypedDict): """Represents a diagnostic, such as a compiler error or warning. Diagnostic objects - are only valid in the scope of a resource.""" + are only valid in the scope of a resource. + """ range: "Range" """ The range at which the message applies """ severity: NotRequired["DiagnosticSeverity"] """ The diagnostic's severity. Can be omitted. If omitted it is up to the client to interpret diagnostics as error, warning, info or hint. """ - code: NotRequired[Union[int, str]] + code: NotRequired[int | str] """ The diagnostic's code, which usually appear in the user interface. """ codeDescription: NotRequired["CodeDescription"] """ An optional property to describe the error code. @@ -3798,11 +3851,11 @@ class Diagnostic(TypedDict): appears in the user interface. """ message: str """ The diagnostic's message. It usually appears in the user interface """ - tags: NotRequired[List["DiagnosticTag"]] + tags: NotRequired[list["DiagnosticTag"]] """ Additional metadata about the diagnostic. @since 3.15.0 """ - relatedInformation: NotRequired[List["DiagnosticRelatedInformation"]] + relatedInformation: NotRequired[list["DiagnosticRelatedInformation"]] """ An array of related diagnostic information, e.g. when symbol-names within a scope collide all definitions can be marked via this property. """ data: NotRequired["LSPAny"] @@ -3825,7 +3878,8 @@ class CompletionContext(TypedDict): class CompletionItemLabelDetails(TypedDict): """Additional details for a completion item label. - @since 3.17.0""" + @since 3.17.0 + """ detail: NotRequired[str] """ An optional string which is rendered less prominently directly after {@link CompletionItem.label label}, @@ -3838,7 +3892,8 @@ class CompletionItemLabelDetails(TypedDict): class InsertReplaceEdit(TypedDict): """A special text edit to provide an insert and a replace operation. - @since 3.16.0""" + @since 3.16.0 + """ newText: str """ The string to be inserted. """ @@ -3851,7 +3906,7 @@ class InsertReplaceEdit(TypedDict): class CompletionOptions(TypedDict): """Completion options.""" - triggerCharacters: NotRequired[List[str]] + triggerCharacters: NotRequired[list[str]] """ Most tools trigger completion request automatically without explicitly requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user starts to type an identifier. For example if the user types `c` in a JavaScript file @@ -3860,7 +3915,7 @@ class CompletionOptions(TypedDict): If code complete should automatically be trigger on characters not being valid inside an identifier (for example `.` in JavaScript) list them in `triggerCharacters`. """ - allCommitCharacters: NotRequired[List[str]] + allCommitCharacters: NotRequired[list[str]] """ The list of all possible characters that commit a completion. This field can be used if clients don't support individual commit characters per completion item. See `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport` @@ -3889,7 +3944,8 @@ class HoverOptions(TypedDict): class SignatureHelpContext(TypedDict): """Additional information about the context in which a signature help request was triggered. - @since 3.15.0""" + @since 3.15.0 + """ triggerKind: "SignatureHelpTriggerKind" """ Action that caused signature help to be triggered. """ @@ -3912,7 +3968,8 @@ class SignatureHelpContext(TypedDict): class SignatureInformation(TypedDict): """Represents the signature of something callable. A signature can have a label, like a function-name, a doc-comment, and - a set of parameters.""" + a set of parameters. + """ label: str """ The label of this signature. Will be shown in @@ -3920,7 +3977,7 @@ class SignatureInformation(TypedDict): documentation: NotRequired[Union[str, "MarkupContent"]] """ The human-readable doc-comment of this signature. Will be shown in the UI but can be omitted. """ - parameters: NotRequired[List["ParameterInformation"]] + parameters: NotRequired[list["ParameterInformation"]] """ The parameters of this signature. """ activeParameter: NotRequired[Uint] """ The index of the active parameter. @@ -3933,9 +3990,9 @@ class SignatureInformation(TypedDict): class SignatureHelpOptions(TypedDict): """Server Capabilities for a {@link SignatureHelpRequest}.""" - triggerCharacters: NotRequired[List[str]] + triggerCharacters: NotRequired[list[str]] """ List of characters that trigger signature help automatically. """ - retriggerCharacters: NotRequired[List[str]] + retriggerCharacters: NotRequired[list[str]] """ List of characters that re-trigger signature help. These trigger characters are only active when signature help is already showing. All trigger characters @@ -3953,7 +4010,8 @@ class DefinitionOptions(TypedDict): class ReferenceContext(TypedDict): """Value-object that contains additional information when - requesting references.""" + requesting references. + """ includeDeclaration: bool """ Include the declaration of the current symbol. """ @@ -3978,7 +4036,7 @@ class BaseSymbolInformation(TypedDict): """ The name of this symbol. """ kind: "SymbolKind" """ The kind of this symbol. """ - tags: NotRequired[List["SymbolTag"]] + tags: NotRequired[list["SymbolTag"]] """ Tags for this symbol. @since 3.16.0 """ @@ -4002,15 +4060,16 @@ class DocumentSymbolOptions(TypedDict): class CodeActionContext(TypedDict): """Contains additional diagnostic information about the context in which - a {@link CodeActionProvider.provideCodeActions code action} is run.""" + a {@link CodeActionProvider.provideCodeActions code action} is run. + """ - diagnostics: List["Diagnostic"] + diagnostics: list["Diagnostic"] """ An array of diagnostics known on the client side overlapping the range provided to the `textDocument/codeAction` request. They are provided so that the server knows which errors are currently presented to the user for the given range. There is no guarantee that these accurately reflect the error state of the resource. The primary parameter to compute code actions is the provided range. """ - only: NotRequired[List["CodeActionKind"]] + only: NotRequired[list["CodeActionKind"]] """ Requested kind of actions to return. Actions not of this kind are filtered out by the client before being shown. So servers @@ -4024,7 +4083,7 @@ class CodeActionContext(TypedDict): class CodeActionOptions(TypedDict): """Provider options for a {@link CodeActionRequest}.""" - codeActionKinds: NotRequired[List["CodeActionKind"]] + codeActionKinds: NotRequired[list["CodeActionKind"]] """ CodeActionKinds that this server may return. The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server @@ -4102,7 +4161,7 @@ class DocumentOnTypeFormattingOptions(TypedDict): firstTriggerCharacter: str """ A character on which formatting should be triggered, like `{`. """ - moreTriggerCharacter: NotRequired[List[str]] + moreTriggerCharacter: NotRequired[list[str]] """ More trigger characters. """ @@ -4119,7 +4178,7 @@ class RenameOptions(TypedDict): class ExecuteCommandOptions(TypedDict): """The server capabilities of a {@link ExecuteCommandRequest}.""" - commands: List[str] + commands: list[str] """ The commands to be executed on the server """ workDoneProgress: NotRequired[bool] @@ -4127,16 +4186,16 @@ class ExecuteCommandOptions(TypedDict): class SemanticTokensLegend(TypedDict): """@since 3.16.0""" - tokenTypes: List[str] + tokenTypes: list[str] """ The token types a server uses. """ - tokenModifiers: List[str] + tokenModifiers: list[str] """ The token modifiers a server uses. """ class OptionalVersionedTextDocumentIdentifier(TypedDict): """A text document identifier to optionally denote a specific version of a text document.""" - version: Union[int, None] + version: int | None """ The version number of this document. If a versioned text document identifier is sent from the server to the client and the file is not open in the editor (the server has not received an open notification before) the server can send @@ -4149,7 +4208,8 @@ class OptionalVersionedTextDocumentIdentifier(TypedDict): class AnnotatedTextEdit(TypedDict): """A special text edit with an additional change annotation. - @since 3.16.0.""" + @since 3.16.0. + """ annotationId: "ChangeAnnotationIdentifier" """ The actual identifier of the change annotation """ @@ -4203,14 +4263,15 @@ class FileOperationPattern(TypedDict): """A pattern to describe in which file operation requests or notifications the server is interested in receiving. - @since 3.16.0""" + @since 3.16.0 + """ glob: str """ The glob pattern to match. Glob patterns can have the following syntax: - `*` to match one or more characters in a path segment - `?` to match on one character in a path segment - `**` to match any number of path segments, including none - - `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) + - `{}` to group sub patterns into an OR expression. (e.g. `**\u200b/*.{ts,js}` matches all TypeScript and JavaScript files) - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) """ matches: NotRequired["FileOperationPatternKind"] @@ -4224,11 +4285,12 @@ class FileOperationPattern(TypedDict): class WorkspaceFullDocumentDiagnosticReport(TypedDict): """A full document diagnostic report for a workspace diagnostic result. - @since 3.17.0""" + @since 3.17.0 + """ uri: "DocumentUri" """ The URI for which diagnostic information is reported. """ - version: Union[int, None] + version: int | None """ The version number for which the diagnostics are reported. If the document is not marked as open `null` can be provided. """ kind: Literal["full"] @@ -4237,18 +4299,19 @@ class WorkspaceFullDocumentDiagnosticReport(TypedDict): """ An optional result id. If provided it will be sent on the next diagnostic request for the same document. """ - items: List["Diagnostic"] + items: list["Diagnostic"] """ The actual items. """ class WorkspaceUnchangedDocumentDiagnosticReport(TypedDict): """An unchanged document diagnostic report for a workspace diagnostic result. - @since 3.17.0""" + @since 3.17.0 + """ uri: "DocumentUri" """ The URI for which diagnostic information is reported. """ - version: Union[int, None] + version: int | None """ The version number for which the diagnostics are reported. If the document is not marked as open `null` can be provided. """ kind: Literal["unchanged"] @@ -4268,7 +4331,8 @@ class NotebookCell(TypedDict): cells and can therefore be used to uniquely identify a notebook cell or the cell's text document. - @since 3.17.0""" + @since 3.17.0 + """ kind: "NotebookCellKind" """ The cell's kind """ @@ -4288,13 +4352,14 @@ class NotebookCellArrayChange(TypedDict): """A change describing how to move a `NotebookCell` array from state S to S'. - @since 3.17.0""" + @since 3.17.0 + """ start: Uint """ The start oftest of the cell that changed. """ deleteCount: Uint """ The deleted cells """ - cells: NotRequired[List["NotebookCell"]] + cells: NotRequired[list["NotebookCell"]] """ The new cells, if any """ @@ -4350,9 +4415,10 @@ class NotebookDocumentSyncOptions(TypedDict): document that contain at least one matching cell will be synced. - @since 3.17.0""" + @since 3.17.0 + """ - notebookSelector: List[ + notebookSelector: list[ Union[ "__NotebookDocumentSyncOptions_notebookSelector_Type_1", "__NotebookDocumentSyncOptions_notebookSelector_Type_2", @@ -4367,9 +4433,10 @@ class NotebookDocumentSyncOptions(TypedDict): class NotebookDocumentSyncRegistrationOptions(TypedDict): """Registration options specific to a notebook. - @since 3.17.0""" + @since 3.17.0 + """ - notebookSelector: List[ + notebookSelector: list[ Union[ "__NotebookDocumentSyncOptions_notebookSelector_Type_3", "__NotebookDocumentSyncOptions_notebookSelector_Type_4", @@ -4387,7 +4454,7 @@ class NotebookDocumentSyncRegistrationOptions(TypedDict): class WorkspaceFoldersServerCapabilities(TypedDict): supported: NotRequired[bool] """ The server has support for workspace folders """ - changeNotifications: NotRequired[Union[str, bool]] + changeNotifications: NotRequired[str | bool] """ Whether the server wants to receive workspace folder change notifications. @@ -4400,7 +4467,8 @@ class WorkspaceFoldersServerCapabilities(TypedDict): class FileOperationOptions(TypedDict): """Options for notifications/requests for user operations on files. - @since 3.16.0""" + @since 3.16.0 + """ didCreate: NotRequired["FileOperationRegistrationOptions"] """ The server is interested in receiving didCreateFiles notifications. """ @@ -4419,7 +4487,8 @@ class FileOperationOptions(TypedDict): class CodeDescription(TypedDict): """Structure to capture a description for an error code. - @since 3.16.0""" + @since 3.16.0 + """ href: "URI" """ An URI to open with more information about the diagnostic error. """ @@ -4428,7 +4497,8 @@ class CodeDescription(TypedDict): class DiagnosticRelatedInformation(TypedDict): """Represents a related message and source code location for a diagnostic. This should be used to point to code locations that cause or related to a diagnostics, e.g when duplicating - a symbol in a scope.""" + a symbol in a scope. + """ location: "Location" """ The location of this related diagnostic information. """ @@ -4438,9 +4508,10 @@ class DiagnosticRelatedInformation(TypedDict): class ParameterInformation(TypedDict): """Represents a parameter of a callable-signature. A parameter can - have a label and a doc-comment.""" + have a label and a doc-comment. + """ - label: Union[str, List[Union[Uint, Uint]]] + label: str | list[Uint | Uint] """ The label of this parameter information. Either a string or an inclusive start and exclusive end offsets within its containing @@ -4458,7 +4529,8 @@ class NotebookCellTextDocumentFilter(TypedDict): """A notebook cell text document filter denotes a cell text document by different properties. - @since 3.17.0""" + @since 3.17.0 + """ notebook: Union[str, "NotebookDocumentFilter"] """ A filter that matches against the notebook @@ -4475,7 +4547,8 @@ class NotebookCellTextDocumentFilter(TypedDict): class FileOperationPatternOptions(TypedDict): """Matching options for the file operation pattern. - @since 3.16.0""" + @since 3.16.0 + """ ignoreCase: NotRequired[bool] """ The pattern should be matched ignoring casing. """ @@ -4644,7 +4717,8 @@ class TextDocumentClientCapabilities(TypedDict): class NotebookDocumentClientCapabilities(TypedDict): """Capabilities specific to the notebook document support. - @since 3.17.0""" + @since 3.17.0 + """ synchronization: "NotebookDocumentSyncClientCapabilities" """ Capabilities specific to notebook document synchronization @@ -4676,11 +4750,10 @@ class WindowClientCapabilities(TypedDict): class GeneralClientCapabilities(TypedDict): """General client capabilities. - @since 3.16.0""" + @since 3.16.0 + """ - staleRequestSupport: NotRequired[ - "__GeneralClientCapabilities_staleRequestSupport_Type_1" - ] + staleRequestSupport: NotRequired["__GeneralClientCapabilities_staleRequestSupport_Type_1"] """ Client capability that signals how the client handles stale requests (e.g. a request for which the client will not process the response @@ -4695,7 +4768,7 @@ class GeneralClientCapabilities(TypedDict): """ Client capabilities specific to the client's markdown parser. @since 3.16.0 """ - positionEncodings: NotRequired[List["PositionEncodingKind"]] + positionEncodings: NotRequired[list["PositionEncodingKind"]] """ The position encodings supported by the client. Client and server have to agree on the same position encoding to ensure that offsets (e.g. character position in a line) are interpreted the same on both @@ -4721,7 +4794,8 @@ class RelativePattern(TypedDict): relatively to a base URI. The common value for a `baseUri` is a workspace folder root, but it can be another absolute URI as well. - @since 3.17.0""" + @since 3.17.0 + """ baseUri: Union["WorkspaceFolder", "URI"] """ A workspace folder or a base URI to which this pattern will be matched @@ -4733,7 +4807,7 @@ class RelativePattern(TypedDict): class WorkspaceEditClientCapabilities(TypedDict): documentChanges: NotRequired[bool] """ The client supports versioned document changes in `WorkspaceEdit`s """ - resourceOperations: NotRequired[List["ResourceOperationKind"]] + resourceOperations: NotRequired[list["ResourceOperationKind"]] """ The resource operations the client supports. Clients should at least support 'create', 'rename' and 'delete' files and folders. @@ -4751,9 +4825,7 @@ class WorkspaceEditClientCapabilities(TypedDict): character. @since 3.16.0 """ - changeAnnotationSupport: NotRequired[ - "__WorkspaceEditClientCapabilities_changeAnnotationSupport_Type_1" - ] + changeAnnotationSupport: NotRequired["__WorkspaceEditClientCapabilities_changeAnnotationSupport_Type_1"] """ Whether the client in general supports change annotations on text edits, create file, rename file and delete file changes. @@ -4789,9 +4861,7 @@ class WorkspaceSymbolClientCapabilities(TypedDict): Clients supporting tags have to handle unknown tags gracefully. @since 3.16.0 """ - resolveSupport: NotRequired[ - "__WorkspaceSymbolClientCapabilities_resolveSupport_Type_1" - ] + resolveSupport: NotRequired["__WorkspaceSymbolClientCapabilities_resolveSupport_Type_1"] """ The client support partial workspace symbols. The client will send the request `workspaceSymbol/resolve` to the server to resolve additional properties. @@ -4838,7 +4908,8 @@ class FileOperationClientCapabilities(TypedDict): These events do not come from the file system, they come from user operations like renaming a file in the UI. - @since 3.16.0""" + @since 3.16.0 + """ dynamicRegistration: NotRequired[bool] """ Whether the client supports dynamic registration for file requests/notifications. """ @@ -4859,7 +4930,8 @@ class FileOperationClientCapabilities(TypedDict): class InlineValueWorkspaceClientCapabilities(TypedDict): """Client workspace capabilities specific to inline values. - @since 3.17.0""" + @since 3.17.0 + """ refreshSupport: NotRequired[bool] """ Whether the client implementation supports a refresh request sent from the @@ -4874,7 +4946,8 @@ class InlineValueWorkspaceClientCapabilities(TypedDict): class InlayHintWorkspaceClientCapabilities(TypedDict): """Client workspace capabilities specific to inlay hints. - @since 3.17.0""" + @since 3.17.0 + """ refreshSupport: NotRequired[bool] """ Whether the client implementation supports a refresh request sent from @@ -4889,7 +4962,8 @@ class InlayHintWorkspaceClientCapabilities(TypedDict): class DiagnosticWorkspaceClientCapabilities(TypedDict): """Workspace client capabilities specific to diagnostic pull requests. - @since 3.17.0""" + @since 3.17.0 + """ refreshSupport: NotRequired[bool] """ Whether the client implementation supports a refresh request sent from @@ -4922,9 +4996,7 @@ class CompletionClientCapabilities(TypedDict): completionItem: NotRequired["__CompletionClientCapabilities_completionItem_Type_1"] """ The client supports the following `CompletionItem` specific capabilities. """ - completionItemKind: NotRequired[ - "__CompletionClientCapabilities_completionItemKind_Type_1" - ] + completionItemKind: NotRequired["__CompletionClientCapabilities_completionItemKind_Type_1"] insertTextMode: NotRequired["InsertTextMode"] """ Defines how the client handles whitespace and indentation when accepting a completion item that uses multi line @@ -4944,7 +5016,7 @@ class CompletionClientCapabilities(TypedDict): class HoverClientCapabilities(TypedDict): dynamicRegistration: NotRequired[bool] """ Whether hover supports dynamic registration. """ - contentFormat: NotRequired[List["MarkupKind"]] + contentFormat: NotRequired[list["MarkupKind"]] """ Client supports the following content formats for the content property. The order describes the preferred format of the client. """ @@ -4954,9 +5026,7 @@ class SignatureHelpClientCapabilities(TypedDict): dynamicRegistration: NotRequired[bool] """ Whether signature help supports dynamic registration. """ - signatureInformation: NotRequired[ - "__SignatureHelpClientCapabilities_signatureInformation_Type_1" - ] + signatureInformation: NotRequired["__SignatureHelpClientCapabilities_signatureInformation_Type_1"] """ The client supports the following `SignatureInformation` specific properties. """ contextSupport: NotRequired[bool] @@ -5058,9 +5128,7 @@ class CodeActionClientCapabilities(TypedDict): dynamicRegistration: NotRequired[bool] """ Whether code action supports dynamic registration. """ - codeActionLiteralSupport: NotRequired[ - "__CodeActionClientCapabilities_codeActionLiteralSupport_Type_1" - ] + codeActionLiteralSupport: NotRequired["__CodeActionClientCapabilities_codeActionLiteralSupport_Type_1"] """ The client support code action literals of type `CodeAction` as a valid response of the `textDocument/codeAction` request. If the property is not set the request can only return `Command` literals. @@ -5180,9 +5248,7 @@ class FoldingRangeClientCapabilities(TypedDict): """ If set, the client signals that it only supports folding complete lines. If set, client will ignore specified `startCharacter` and `endCharacter` properties in a FoldingRange. """ - foldingRangeKind: NotRequired[ - "__FoldingRangeClientCapabilities_foldingRangeKind_Type_1" - ] + foldingRangeKind: NotRequired["__FoldingRangeClientCapabilities_foldingRangeKind_Type_1"] """ Specific options for the folding range kind. @since 3.17.0 """ @@ -5251,11 +5317,11 @@ class SemanticTokensClientCapabilities(TypedDict): `request.range` are both set to true but the server only provides a range provider the client might not render a minimap correctly or might even decide to not show any semantic tokens at all. """ - tokenTypes: List[str] + tokenTypes: list[str] """ The token types that the client supports. """ - tokenModifiers: List[str] + tokenModifiers: list[str] """ The token modifiers that the client supports. """ - formats: List["TokenFormat"] + formats: list["TokenFormat"] """ The token formats the clients supports. """ overlappingTokenSupport: NotRequired[bool] """ Whether the client supports tokens that can overlap each other. """ @@ -5284,7 +5350,8 @@ class SemanticTokensClientCapabilities(TypedDict): class LinkedEditingRangeClientCapabilities(TypedDict): """Client capabilities for the linked editing range request. - @since 3.16.0""" + @since 3.16.0 + """ dynamicRegistration: NotRequired[bool] """ Whether implementation supports dynamic registration. If this is set to `true` @@ -5295,7 +5362,8 @@ class LinkedEditingRangeClientCapabilities(TypedDict): class MonikerClientCapabilities(TypedDict): """Client capabilities specific to the moniker request. - @since 3.16.0""" + @since 3.16.0 + """ dynamicRegistration: NotRequired[bool] """ Whether moniker supports dynamic registration. If this is set to `true` @@ -5315,7 +5383,8 @@ class TypeHierarchyClientCapabilities(TypedDict): class InlineValueClientCapabilities(TypedDict): """Client capabilities specific to inline values. - @since 3.17.0""" + @since 3.17.0 + """ dynamicRegistration: NotRequired[bool] """ Whether implementation supports dynamic registration for inline value providers. """ @@ -5324,7 +5393,8 @@ class InlineValueClientCapabilities(TypedDict): class InlayHintClientCapabilities(TypedDict): """Inlay hint client capabilities. - @since 3.17.0""" + @since 3.17.0 + """ dynamicRegistration: NotRequired[bool] """ Whether inlay hints support dynamic registration. """ @@ -5336,7 +5406,8 @@ class InlayHintClientCapabilities(TypedDict): class DiagnosticClientCapabilities(TypedDict): """Client capabilities specific to diagnostic pull requests. - @since 3.17.0""" + @since 3.17.0 + """ dynamicRegistration: NotRequired[bool] """ Whether implementation supports dynamic registration. If this is set to `true` @@ -5349,7 +5420,8 @@ class DiagnosticClientCapabilities(TypedDict): class NotebookDocumentSyncClientCapabilities(TypedDict): """Notebook specific client capabilities. - @since 3.17.0""" + @since 3.17.0 + """ dynamicRegistration: NotRequired[bool] """ Whether implementation supports dynamic registration. If this is @@ -5363,16 +5435,15 @@ class NotebookDocumentSyncClientCapabilities(TypedDict): class ShowMessageRequestClientCapabilities(TypedDict): """Show message request client capabilities""" - messageActionItem: NotRequired[ - "__ShowMessageRequestClientCapabilities_messageActionItem_Type_1" - ] + messageActionItem: NotRequired["__ShowMessageRequestClientCapabilities_messageActionItem_Type_1"] """ Capabilities specific to the `MessageActionItem` type. """ class ShowDocumentClientCapabilities(TypedDict): """Client capabilities for the showDocument request. - @since 3.16.0""" + @since 3.16.0 + """ support: bool """ The client has support for the showDocument @@ -5382,7 +5453,8 @@ class ShowDocumentClientCapabilities(TypedDict): class RegularExpressionsClientCapabilities(TypedDict): """Client capabilities specific to regular expressions. - @since 3.16.0""" + @since 3.16.0 + """ engine: str """ The engine's name. """ @@ -5393,13 +5465,14 @@ class RegularExpressionsClientCapabilities(TypedDict): class MarkdownClientCapabilities(TypedDict): """Client capabilities specific to the used markdown parser. - @since 3.16.0""" + @since 3.16.0 + """ parser: str """ The name of the parser. """ version: NotRequired[str] """ The version of the parser. """ - allowedTags: NotRequired[List[str]] + allowedTags: NotRequired[list[str]] """ A list of HTML tags that the client allows / supports in Markdown. @@ -5412,10 +5485,8 @@ class __CodeActionClientCapabilities_codeActionLiteralSupport_Type_1(TypedDict): set. """ -class __CodeActionClientCapabilities_codeActionLiteralSupport_codeActionKind_Type_1( - TypedDict -): - valueSet: List["CodeActionKind"] +class __CodeActionClientCapabilities_codeActionLiteralSupport_codeActionKind_Type_1(TypedDict): + valueSet: list["CodeActionKind"] """ The code action kind values the client supports. When this property exists the client also guarantees that it will handle values outside its set gracefully and falls back @@ -5423,7 +5494,7 @@ class __CodeActionClientCapabilities_codeActionLiteralSupport_codeActionKind_Typ class __CodeActionClientCapabilities_resolveSupport_Type_1(TypedDict): - properties: List[str] + properties: list[str] """ The properties that a client can resolve lazily. """ @@ -5435,7 +5506,7 @@ class __CodeAction_disabled_Type_1(TypedDict): class __CompletionClientCapabilities_completionItemKind_Type_1(TypedDict): - valueSet: NotRequired[List["CompletionItemKind"]] + valueSet: NotRequired[list["CompletionItemKind"]] """ The completion item kind values the client supports. When this property exists the client also guarantees that it will handle values outside its set gracefully and falls back @@ -5456,16 +5527,14 @@ class __CompletionClientCapabilities_completionItem_Type_1(TypedDict): that is typing in one will update others too. """ commitCharactersSupport: NotRequired[bool] """ Client supports commit characters on a completion item. """ - documentationFormat: NotRequired[List["MarkupKind"]] + documentationFormat: NotRequired[list["MarkupKind"]] """ Client supports the following content formats for the documentation property. The order describes the preferred format of the client. """ deprecatedSupport: NotRequired[bool] """ Client supports the deprecated property on a completion item. """ preselectSupport: NotRequired[bool] """ Client supports the preselect property on a completion item. """ - tagSupport: NotRequired[ - "__CompletionClientCapabilities_completionItem_tagSupport_Type_1" - ] + tagSupport: NotRequired["__CompletionClientCapabilities_completionItem_tagSupport_Type_1"] """ Client supports the tag property on a completion item. Clients supporting tags have to handle unknown tags gracefully. Clients especially need to preserve unknown tags when sending a completion item back to the server in @@ -5477,17 +5546,13 @@ class __CompletionClientCapabilities_completionItem_Type_1(TypedDict): completion item is inserted in the text or should replace text. @since 3.16.0 """ - resolveSupport: NotRequired[ - "__CompletionClientCapabilities_completionItem_resolveSupport_Type_1" - ] + resolveSupport: NotRequired["__CompletionClientCapabilities_completionItem_resolveSupport_Type_1"] """ Indicates which properties a client can resolve lazily on a completion item. Before version 3.16.0 only the predefined properties `documentation` and `details` could be resolved lazily. @since 3.16.0 """ - insertTextModeSupport: NotRequired[ - "__CompletionClientCapabilities_completionItem_insertTextModeSupport_Type_1" - ] + insertTextModeSupport: NotRequired["__CompletionClientCapabilities_completionItem_insertTextModeSupport_Type_1"] """ The client supports the `insertTextMode` property on a completion item to override the whitespace handling mode as defined by the client (see `insertTextMode`). @@ -5500,24 +5565,22 @@ class __CompletionClientCapabilities_completionItem_Type_1(TypedDict): @since 3.17.0 """ -class __CompletionClientCapabilities_completionItem_insertTextModeSupport_Type_1( - TypedDict -): - valueSet: List["InsertTextMode"] +class __CompletionClientCapabilities_completionItem_insertTextModeSupport_Type_1(TypedDict): + valueSet: list["InsertTextMode"] class __CompletionClientCapabilities_completionItem_resolveSupport_Type_1(TypedDict): - properties: List[str] + properties: list[str] """ The properties that a client can resolve lazily. """ class __CompletionClientCapabilities_completionItem_tagSupport_Type_1(TypedDict): - valueSet: List["CompletionItemTag"] + valueSet: list["CompletionItemTag"] """ The tags supported by the client. """ class __CompletionClientCapabilities_completionList_Type_1(TypedDict): - itemDefaults: NotRequired[List[str]] + itemDefaults: NotRequired[list[str]] """ The client supports the following itemDefaults on a completion list. @@ -5529,13 +5592,11 @@ class __CompletionClientCapabilities_completionList_Type_1(TypedDict): class __CompletionList_itemDefaults_Type_1(TypedDict): - commitCharacters: NotRequired[List[str]] + commitCharacters: NotRequired[list[str]] """ A default commit character set. @since 3.17.0 """ - editRange: NotRequired[ - Union["Range", "__CompletionList_itemDefaults_editRange_Type_1"] - ] + editRange: NotRequired[Union["Range", "__CompletionList_itemDefaults_editRange_Type_1"]] """ A default edit range. @since 3.17.0 """ @@ -5577,7 +5638,7 @@ class __CompletionOptions_completionItem_Type_2(TypedDict): class __DocumentSymbolClientCapabilities_symbolKind_Type_1(TypedDict): - valueSet: NotRequired[List["SymbolKind"]] + valueSet: NotRequired[list["SymbolKind"]] """ The symbol kind values the client supports. When this property exists the client also guarantees that it will handle values outside its set gracefully and falls back @@ -5589,12 +5650,12 @@ class __DocumentSymbolClientCapabilities_symbolKind_Type_1(TypedDict): class __DocumentSymbolClientCapabilities_tagSupport_Type_1(TypedDict): - valueSet: List["SymbolTag"] + valueSet: list["SymbolTag"] """ The tags supported by the client. """ class __FoldingRangeClientCapabilities_foldingRangeKind_Type_1(TypedDict): - valueSet: NotRequired[List["FoldingRangeKind"]] + valueSet: NotRequired[list["FoldingRangeKind"]] """ The folding range kind values the client supports. When this property exists the client also guarantees that it will handle values outside its set gracefully and falls back @@ -5612,7 +5673,7 @@ class __FoldingRangeClientCapabilities_foldingRange_Type_1(TypedDict): class __GeneralClientCapabilities_staleRequestSupport_Type_1(TypedDict): cancel: bool """ The client will actively cancel the request. """ - retryOnContentModified: List[str] + retryOnContentModified: list[str] """ The list of requests for which the client will retry the request if it receives a response with error code `ContentModified` """ @@ -5626,7 +5687,7 @@ class __InitializeResult_serverInfo_Type_1(TypedDict): class __InlayHintClientCapabilities_resolveSupport_Type_1(TypedDict): - properties: List[str] + properties: list[str] """ The properties that a client can resolve lazily. """ @@ -5639,27 +5700,25 @@ class __NotebookDocumentChangeEvent_cells_Type_1(TypedDict): structure: NotRequired["__NotebookDocumentChangeEvent_cells_structure_Type_1"] """ Changes to the cell structure to add or remove cells. """ - data: NotRequired[List["NotebookCell"]] + data: NotRequired[list["NotebookCell"]] """ Changes to notebook cells properties like its kind, execution summary or metadata. """ - textContent: NotRequired[ - List["__NotebookDocumentChangeEvent_cells_textContent_Type_1"] - ] + textContent: NotRequired[list["__NotebookDocumentChangeEvent_cells_textContent_Type_1"]] """ Changes to the text content of notebook cells. """ class __NotebookDocumentChangeEvent_cells_structure_Type_1(TypedDict): array: "NotebookCellArrayChange" """ The change to the cell array. """ - didOpen: NotRequired[List["TextDocumentItem"]] + didOpen: NotRequired[list["TextDocumentItem"]] """ Additional opened cell text documents. """ - didClose: NotRequired[List["TextDocumentIdentifier"]] + didClose: NotRequired[list["TextDocumentIdentifier"]] """ Additional closed cell text documents. """ class __NotebookDocumentChangeEvent_cells_textContent_Type_1(TypedDict): document: "VersionedTextDocumentIdentifier" - changes: List["TextDocumentContentChangeEvent"] + changes: list["TextDocumentContentChangeEvent"] class __NotebookDocumentFilter_Type_1(TypedDict): @@ -5694,9 +5753,7 @@ class __NotebookDocumentSyncOptions_notebookSelector_Type_1(TypedDict): """ The notebook to be synced If a string value is provided it matches against the notebook type. '*' matches every notebook. """ - cells: NotRequired[ - List["__NotebookDocumentSyncOptions_notebookSelector_cells_Type_1"] - ] + cells: NotRequired[list["__NotebookDocumentSyncOptions_notebookSelector_cells_Type_1"]] """ The cells of the matching notebook to be synced. """ @@ -5705,7 +5762,7 @@ class __NotebookDocumentSyncOptions_notebookSelector_Type_2(TypedDict): """ The notebook to be synced If a string value is provided it matches against the notebook type. '*' matches every notebook. """ - cells: List["__NotebookDocumentSyncOptions_notebookSelector_cells_Type_2"] + cells: list["__NotebookDocumentSyncOptions_notebookSelector_cells_Type_2"] """ The cells of the matching notebook to be synced. """ @@ -5714,9 +5771,7 @@ class __NotebookDocumentSyncOptions_notebookSelector_Type_3(TypedDict): """ The notebook to be synced If a string value is provided it matches against the notebook type. '*' matches every notebook. """ - cells: NotRequired[ - List["__NotebookDocumentSyncOptions_notebookSelector_cells_Type_3"] - ] + cells: NotRequired[list["__NotebookDocumentSyncOptions_notebookSelector_cells_Type_3"]] """ The cells of the matching notebook to be synced. """ @@ -5725,7 +5780,7 @@ class __NotebookDocumentSyncOptions_notebookSelector_Type_4(TypedDict): """ The notebook to be synced If a string value is provided it matches against the notebook type. '*' matches every notebook. """ - cells: List["__NotebookDocumentSyncOptions_notebookSelector_cells_Type_4"] + cells: list["__NotebookDocumentSyncOptions_notebookSelector_cells_Type_4"] """ The cells of the matching notebook to be synced. """ @@ -5755,17 +5810,15 @@ class __PrepareRenameResult_Type_2(TypedDict): class __PublishDiagnosticsClientCapabilities_tagSupport_Type_1(TypedDict): - valueSet: List["DiagnosticTag"] + valueSet: list["DiagnosticTag"] """ The tags supported by the client. """ class __SemanticTokensClientCapabilities_requests_Type_1(TypedDict): - range: NotRequired[Union[bool, dict]] + range: NotRequired[bool | dict] """ The client will send the `textDocument/semanticTokens/range` request if the server provides a corresponding handler. """ - full: NotRequired[ - Union[bool, "__SemanticTokensClientCapabilities_requests_full_Type_1"] - ] + full: NotRequired[Union[bool, "__SemanticTokensClientCapabilities_requests_full_Type_1"]] """ The client will send the `textDocument/semanticTokens/full` request if the server provides a corresponding handler. """ @@ -5805,12 +5858,10 @@ class __ShowMessageRequestClientCapabilities_messageActionItem_Type_1(TypedDict) class __SignatureHelpClientCapabilities_signatureInformation_Type_1(TypedDict): - documentationFormat: NotRequired[List["MarkupKind"]] + documentationFormat: NotRequired[list["MarkupKind"]] """ Client supports the following content formats for the documentation property. The order describes the preferred format of the client. """ - parameterInformation: NotRequired[ - "__SignatureHelpClientCapabilities_signatureInformation_parameterInformation_Type_1" - ] + parameterInformation: NotRequired["__SignatureHelpClientCapabilities_signatureInformation_parameterInformation_Type_1"] """ Client capabilities specific to parameter information. """ activeParameterSupport: NotRequired[bool] """ The client supports the `activeParameter` property on `SignatureInformation` @@ -5819,9 +5870,7 @@ class __SignatureHelpClientCapabilities_signatureInformation_Type_1(TypedDict): @since 3.16.0 """ -class __SignatureHelpClientCapabilities_signatureInformation_parameterInformation_Type_1( - TypedDict -): +class __SignatureHelpClientCapabilities_signatureInformation_parameterInformation_Type_1(TypedDict): labelOffsetSupport: NotRequired[bool] """ The client supports processing label offsets instead of a simple label string. @@ -5880,13 +5929,13 @@ class __WorkspaceEditClientCapabilities_changeAnnotationSupport_Type_1(TypedDict class __WorkspaceSymbolClientCapabilities_resolveSupport_Type_1(TypedDict): - properties: List[str] + properties: list[str] """ The properties that a client can resolve lazily. Usually `location.range` """ class __WorkspaceSymbolClientCapabilities_symbolKind_Type_1(TypedDict): - valueSet: NotRequired[List["SymbolKind"]] + valueSet: NotRequired[list["SymbolKind"]] """ The symbol kind values the client supports. When this property exists the client also guarantees that it will handle values outside its set gracefully and falls back @@ -5898,7 +5947,7 @@ class __WorkspaceSymbolClientCapabilities_symbolKind_Type_1(TypedDict): class __WorkspaceSymbolClientCapabilities_tagSupport_Type_1(TypedDict): - valueSet: List["SymbolTag"] + valueSet: list["SymbolTag"] """ The tags supported by the client. """ diff --git a/src/solidlsp/lsp_protocol_handler/server.py b/src/solidlsp/lsp_protocol_handler/server.py new file mode 100644 index 00000000..0a001f44 --- /dev/null +++ b/src/solidlsp/lsp_protocol_handler/server.py @@ -0,0 +1,130 @@ +""" +This file provides the implementation of the JSON-RPC client, that launches and +communicates with the language server. + +The initial implementation of this file was obtained from +https://github.com/predragnikolic/OLSP under the MIT License with the following terms: + +MIT License + +Copyright (c) 2023 Предраг Николић + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import asyncio +import dataclasses +import json +import logging +import os +import threading +from collections.abc import Callable +from typing import Any, Union + +import psutil + +from ..ls_exceptions import LanguageServerException +from .lsp_requests import LspNotification, LspRequest +from .lsp_types import ErrorCodes + +StringDict = dict[str, Any] +PayloadLike = Union[list[StringDict], StringDict, None] +CONTENT_LENGTH = "Content-Length: " +ENCODING = "utf-8" +log = logging.getLogger(__name__) + + +@dataclasses.dataclass +class ProcessLaunchInfo: + """ + This class is used to store the information required to launch a process. + """ + + # The command to launch the process + cmd: str + + # The environment variables to set for the process + env: dict[str, str] = dataclasses.field(default_factory=dict) + + # The working directory for the process + cwd: str = os.getcwd() + + +class Error(Exception): + def __init__(self, code: ErrorCodes, message: str) -> None: + super().__init__(message) + self.code = code + + def to_lsp(self) -> StringDict: + return {"code": self.code, "message": super().__str__()} + + @classmethod + def from_lsp(cls, d: StringDict) -> "Error": + return Error(d["code"], d["message"]) + + def __str__(self) -> str: + return f"{super().__str__()} ({self.code})" + + +def make_response(request_id: Any, params: PayloadLike) -> StringDict: + return {"jsonrpc": "2.0", "id": request_id, "result": params} + + +def make_error_response(request_id: Any, err: Error) -> StringDict: + return {"jsonrpc": "2.0", "id": request_id, "error": err.to_lsp()} + + +def make_notification(method: str, params: PayloadLike) -> StringDict: + return {"jsonrpc": "2.0", "method": method, "params": params} + + +def make_request(method: str, request_id: Any, params: PayloadLike) -> StringDict: + return {"jsonrpc": "2.0", "method": method, "id": request_id, "params": params} + + +class StopLoopException(Exception): + pass + + +def create_message(payload: PayloadLike): + body = json.dumps(payload, check_circular=False, ensure_ascii=False, separators=(",", ":")).encode(ENCODING) + return ( + f"Content-Length: {len(body)}\r\n".encode(ENCODING), + "Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n".encode(ENCODING), + body, + ) + + +class MessageType: + error = 1 + warning = 2 + info = 3 + log = 4 + + +def content_length(line: bytes) -> int | None: + if line.startswith(b"Content-Length: "): + _, value = line.split(b"Content-Length: ") + value = value.strip() + try: + return int(value) + except ValueError: + raise ValueError(f"Invalid Content-Length header: {value}") + return None + diff --git a/src/multilspy/multilspy_settings.py b/src/solidlsp/settings.py similarity index 96% rename from src/multilspy/multilspy_settings.py rename to src/solidlsp/settings.py index 5d7ebfab..166be83c 100644 --- a/src/multilspy/multilspy_settings.py +++ b/src/solidlsp/settings.py @@ -5,10 +5,12 @@ Defines the settings for multilspy. import os import pathlib -class MultilspySettings: + +class SolidLSPSettings: """ Provides the various settings for multilspy. """ + @staticmethod def get_language_server_directory() -> str: """Returns the directory for language servers""" diff --git a/test/conftest.py b/test/conftest.py index 75bc8c74..6632f3ac 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,16 +1,13 @@ import logging from pathlib import Path -from typing import cast import pytest from sensai.util.logging import configure -from multilspy.language_server import SyncLanguageServer -from multilspy.multilspy_config import Language, MultilspyConfig -from multilspy.multilspy_logger import MultilspyLogger -from serena.constants import USE_SOLID_LSP 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 configure(level=logging.DEBUG) @@ -36,24 +33,19 @@ def create_ls( ignored_paths: list[str] | None = None, trace_lsp_communication: bool = False, log_level: int = logging.INFO, -) -> SyncLanguageServer: +) -> SolidLanguageServer: ignored_paths = ignored_paths or [] if repo_path is None: repo_path = str(get_repo_path(language)) gitignore_parser = GitignoreParser(str(repo_path)) for spec in gitignore_parser.get_ignore_specs(): ignored_paths.extend(spec.patterns) - config = MultilspyConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication) - logger = MultilspyLogger(log_level=log_level) - - if USE_SOLID_LSP: - ls = SolidLanguageServer.create(config, logger, repo_path) - return cast(SyncLanguageServer, ls) # TODO: Fix type - else: - return SyncLanguageServer.create(config, logger, repo_path) + 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) -def create_default_ls(language: Language) -> SyncLanguageServer: +def create_default_ls(language: Language) -> SolidLanguageServer: repo_path = str(get_repo_path(language)) return create_ls(language, repo_path) diff --git a/test/multilspy/go/test_go_basic.py b/test/multilspy/go/test_go_basic.py index 6b5e316b..56323157 100644 --- a/test/multilspy/go/test_go_basic.py +++ b/test/multilspy/go/test_go_basic.py @@ -2,22 +2,22 @@ import os import pytest -from multilspy import SyncLanguageServer -from multilspy.multilspy_config import Language -from multilspy.multilspy_utils import SymbolUtils +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language +from solidlsp.ls_utils import SymbolUtils @pytest.mark.go class TestGoLanguageServer: @pytest.mark.parametrize("language_server", [Language.GO], indirect=True) - def test_find_symbol(self, language_server: SyncLanguageServer) -> None: + def test_find_symbol(self, language_server: SolidLanguageServer) -> None: symbols = language_server.request_full_symbol_tree() assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Helper"), "Helper function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoStruct"), "DemoStruct not found in symbol tree" @pytest.mark.parametrize("language_server", [Language.GO], indirect=True) - def test_find_referencing_symbols(self, language_server: SyncLanguageServer) -> None: + def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: file_path = os.path.join("main.go") symbols = language_server.request_document_symbols(file_path) helper_symbol = None diff --git a/test/multilspy/java/test_java_basic.py b/test/multilspy/java/test_java_basic.py index e7ff5a8c..d74a9fea 100644 --- a/test/multilspy/java/test_java_basic.py +++ b/test/multilspy/java/test_java_basic.py @@ -2,22 +2,22 @@ import os import pytest -from multilspy import SyncLanguageServer -from multilspy.multilspy_config import Language -from multilspy.multilspy_utils import SymbolUtils +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language +from solidlsp.ls_utils import SymbolUtils @pytest.mark.java class TestJavaLanguageServer: @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True) - def test_find_symbol(self, language_server: SyncLanguageServer) -> None: + def test_find_symbol(self, language_server: SolidLanguageServer) -> None: symbols = language_server.request_full_symbol_tree() assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree" @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True) - def test_find_referencing_symbols(self, language_server: SyncLanguageServer) -> None: + def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: # Use correct Maven/Java file paths file_path = os.path.join("src", "main", "java", "test_repo", "Utils.java") refs = language_server.request_references(file_path, 4, 20) @@ -43,7 +43,7 @@ class TestJavaLanguageServer: ), "Main should reference Model (tried all positions in selectionRange)" @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True) - def test_overview_methods(self, language_server: SyncLanguageServer) -> None: + def test_overview_methods(self, language_server: SolidLanguageServer) -> None: symbols = language_server.request_full_symbol_tree() assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview" assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview" diff --git a/test/multilspy/php/test_php_basic.py b/test/multilspy/php/test_php_basic.py index 942281fe..391c565b 100644 --- a/test/multilspy/php/test_php_basic.py +++ b/test/multilspy/php/test_php_basic.py @@ -2,15 +2,15 @@ from pathlib import Path import pytest -from multilspy.language_server import SyncLanguageServer -from multilspy.multilspy_config import Language +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language @pytest.mark.php class TestPhpLanguageServer: @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True) @pytest.mark.parametrize("repo_path", [Language.PHP], indirect=True) - def test_ls_is_running(self, language_server: SyncLanguageServer, repo_path: Path) -> None: + def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None: """Test that the language server starts and stops successfully.""" # The fixture already handles start and stop assert language_server.is_running() @@ -18,7 +18,7 @@ class TestPhpLanguageServer: @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True) @pytest.mark.parametrize("repo_path", [Language.PHP], indirect=True) - def test_find_definition_within_file(self, language_server: SyncLanguageServer, repo_path: Path) -> None: + def test_find_definition_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None: # In index.php: # Line 9 (1-indexed): $greeting = greet($userName); @@ -41,7 +41,7 @@ class TestPhpLanguageServer: @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True) @pytest.mark.parametrize("repo_path", [Language.PHP], indirect=True) - def test_find_definition_across_files(self, language_server: SyncLanguageServer, repo_path: Path) -> None: + def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: definition_location_list = language_server.request_definition(str(repo_path / "index.php"), 12, 5) # helperFunction assert definition_location_list, f"Expected non-empty definition_location_list but got {definition_location_list=}" @@ -53,7 +53,7 @@ class TestPhpLanguageServer: @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True) @pytest.mark.parametrize("repo_path", [Language.PHP], indirect=True) - def test_find_definition_simple_variable(self, language_server: SyncLanguageServer, repo_path: Path) -> None: + def test_find_definition_simple_variable(self, language_server: SolidLanguageServer, repo_path: Path) -> None: file_path = str(repo_path / "simple_var.php") # In simple_var.php: @@ -74,7 +74,7 @@ class TestPhpLanguageServer: @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True) @pytest.mark.parametrize("repo_path", [Language.PHP], indirect=True) - def test_find_references_within_file(self, language_server: SyncLanguageServer, repo_path: Path) -> None: + def test_find_references_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None: index_php_path = str(repo_path / "index.php") # In index.php (0-indexed lines): @@ -108,7 +108,7 @@ class TestPhpLanguageServer: @pytest.mark.parametrize("language_server", [Language.PHP], indirect=True) @pytest.mark.parametrize("repo_path", [Language.PHP], indirect=True) - def test_find_references_across_files(self, language_server: SyncLanguageServer, repo_path: Path) -> None: + def test_find_references_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: helper_php_path = str(repo_path / "helper.php") # In index.php (0-indexed lines): # Line 13: helperFunction(); // Usage of helperFunction diff --git a/test/multilspy/python/test_python_basic.py b/test/multilspy/python/test_python_basic.py index 5af4f953..74737c47 100644 --- a/test/multilspy/python/test_python_basic.py +++ b/test/multilspy/python/test_python_basic.py @@ -9,9 +9,9 @@ import os import pytest -from multilspy.language_server import SyncLanguageServer -from multilspy.multilspy_config import Language from serena.text_utils import LineType +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language @pytest.mark.python @@ -19,7 +19,7 @@ class TestLanguageServerBasics: """Test basic functionality of the language server.""" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_references_user_class(self, language_server: SyncLanguageServer) -> None: + def test_request_references_user_class(self, language_server: SolidLanguageServer) -> None: """Test request_references on the User class.""" # Get references to the User class in models.py file_path = os.path.join("test_repo", "models.py") @@ -34,7 +34,7 @@ class TestLanguageServerBasics: assert len(references) > 1, "User class should be referenced in multiple files (using selectionRange if present)" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_references_item_class(self, language_server: SyncLanguageServer) -> None: + def test_request_references_item_class(self, language_server: SolidLanguageServer) -> None: """Test request_references on the Item class.""" # Get references to the Item class in models.py file_path = os.path.join("test_repo", "models.py") @@ -50,7 +50,7 @@ class TestLanguageServerBasics: assert len(services_references) > 0, "At least one reference should be in services.py (using selectionRange if present)" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_references_function_parameter(self, language_server: SyncLanguageServer) -> None: + def test_request_references_function_parameter(self, language_server: SolidLanguageServer) -> None: """Test request_references on a function parameter.""" # Get references to the id parameter in get_user method file_path = os.path.join("test_repo", "services.py") @@ -65,7 +65,7 @@ class TestLanguageServerBasics: assert len(references) > 0, "id parameter should be referenced within the method (using selectionRange if present)" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_references_create_user_method(self, language_server: SyncLanguageServer) -> None: + def test_request_references_create_user_method(self, language_server: SolidLanguageServer) -> None: # Get references to the create_user method in UserService file_path = os.path.join("test_repo", "services.py") # Line 15 contains the create_user method definition @@ -79,7 +79,7 @@ class TestLanguageServerBasics: assert len(references) > 1, "Should get valid references for create_user (using selectionRange if present)" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_retrieve_content_around_line(self, language_server: SyncLanguageServer) -> None: + def test_retrieve_content_around_line(self, language_server: SolidLanguageServer) -> None: """Test retrieve_content_around_line functionality with various scenarios.""" file_path = os.path.join("test_repo", "models.py") @@ -186,7 +186,7 @@ class TestLanguageServerBasics: assert line.match_type == LineType.AFTER_MATCH @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_search_files_for_pattern(self, language_server: SyncLanguageServer) -> None: + def test_search_files_for_pattern(self, language_server: SolidLanguageServer) -> None: """Test search_files_for_pattern with various patterns and glob filters.""" # Test 1: Search for class definitions across all files class_pattern = r"class\s+\w+\s*(?:\([^{]*\)|:)" diff --git a/test/multilspy/python/test_retrieval_with_ignored_dirs.py b/test/multilspy/python/test_retrieval_with_ignored_dirs.py index 0db668ef..a3d7d6a2 100644 --- a/test/multilspy/python/test_retrieval_with_ignored_dirs.py +++ b/test/multilspy/python/test_retrieval_with_ignored_dirs.py @@ -3,8 +3,8 @@ from pathlib import Path import pytest -from multilspy.language_server import SyncLanguageServer -from multilspy.multilspy_config import Language +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language from test.conftest import create_ls # This mark will be applied to all tests in this module @@ -12,7 +12,7 @@ pytestmark = pytest.mark.python @pytest.fixture(scope="module") -def ls_with_ignored_dirs() -> Generator[SyncLanguageServer, None, None]: +def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]: """Fixture to set up an LS for the python test repo with the 'scripts' directory ignored.""" ignored_paths = ["scripts", "custom_test"] ls = create_ls(ignored_paths=ignored_paths, language=Language.PYTHON) @@ -24,7 +24,7 @@ def ls_with_ignored_dirs() -> Generator[SyncLanguageServer, None, None]: @pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True) -def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SyncLanguageServer): +def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer): """Tests that request_full_symbol_tree ignores the configured directory.""" root = ls_with_ignored_dirs.request_full_symbol_tree()[0] root_children = root["children"] @@ -33,7 +33,7 @@ def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SyncLanguageServer): @pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True) -def test_find_references_ignores_dir(ls_with_ignored_dirs: SyncLanguageServer): +def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer): """Tests that find_references ignores the configured directory.""" # Location of Item, which is referenced in scripts definition_file = "test_repo/models.py" diff --git a/test/multilspy/python/test_symbol_retrieval.py b/test/multilspy/python/test_symbol_retrieval.py index ba5bd615..c19bf8bb 100644 --- a/test/multilspy/python/test_symbol_retrieval.py +++ b/test/multilspy/python/test_symbol_retrieval.py @@ -10,9 +10,9 @@ import os import pytest -from multilspy.language_server import SyncLanguageServer -from multilspy.multilspy_config import Language -from multilspy.multilspy_types import SymbolKind +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language +from solidlsp.ls_types import SymbolKind pytestmark = pytest.mark.python @@ -21,7 +21,7 @@ class TestLanguageServerSymbols: """Test the language server's symbol-related functionality.""" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_containing_symbol_function(self, language_server: SyncLanguageServer) -> None: + def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol for a function.""" # Test for a position inside the create_user method file_path = os.path.join("test_repo", "services.py") @@ -36,7 +36,7 @@ class TestLanguageServerSymbols: assert containing_symbol["body"].strip().startswith("def create_user(self") @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_references_to_variables(self, language_server: SyncLanguageServer) -> None: + def test_references_to_variables(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a variable.""" file_path = os.path.join("test_repo", "variables.py") # Line 75 contains the field status that is later modified @@ -51,7 +51,7 @@ class TestLanguageServerSymbols: assert "second_dataclass" in ref_names @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_containing_symbol_class(self, language_server: SyncLanguageServer) -> None: + def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol for a class.""" # Test for a position inside the UserService class but outside any method file_path = os.path.join("test_repo", "services.py") @@ -64,7 +64,7 @@ class TestLanguageServerSymbols: assert containing_symbol["kind"] == SymbolKind.Class @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_containing_symbol_nested(self, language_server: SyncLanguageServer) -> None: + def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol with nested scopes.""" # Test for a position inside a method which is inside a class file_path = os.path.join("test_repo", "services.py") @@ -90,7 +90,7 @@ class TestLanguageServerSymbols: assert parent_symbol["kind"] == SymbolKind.Class @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_containing_symbol_none(self, language_server: SyncLanguageServer) -> None: + def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol for a position with no containing symbol.""" # Test for a position outside any function/class (e.g., in imports) file_path = os.path.join("test_repo", "services.py") @@ -101,7 +101,7 @@ class TestLanguageServerSymbols: assert containing_symbol is None or containing_symbol == {} @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_referencing_symbols_function(self, language_server: SyncLanguageServer) -> None: + def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a function.""" # Test referencing symbols for create_user function file_path = os.path.join("test_repo", "services.py") @@ -125,7 +125,7 @@ class TestLanguageServerSymbols: assert "end" in symbol["location"]["range"] @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_referencing_symbols_class(self, language_server: SyncLanguageServer) -> None: + def test_request_referencing_symbols_class(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a class.""" # Test referencing symbols for User class file_path = os.path.join("test_repo", "models.py") @@ -146,7 +146,7 @@ class TestLanguageServerSymbols: assert len(services_references) > 0, "No referencing symbols from services.py for User (selectionRange)" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_referencing_symbols_parameter(self, language_server: SyncLanguageServer) -> None: + def test_request_referencing_symbols_parameter(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a function parameter.""" # Test referencing symbols for id parameter in get_user file_path = os.path.join("test_repo", "services.py") @@ -167,7 +167,7 @@ class TestLanguageServerSymbols: assert len(method_refs) > 0, "No referencing symbols within method body for get_user (selectionRange)" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_referencing_symbols_none(self, language_server: SyncLanguageServer) -> None: + def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a position with no symbol.""" # For positions with no symbol, the method might throw an error or return None/empty list # We'll modify our test to handle this by using a try-except block @@ -185,7 +185,7 @@ class TestLanguageServerSymbols: # Tests for request_defining_symbol @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_defining_symbol_variable(self, language_server: SyncLanguageServer) -> None: + def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for a variable usage.""" # Test finding the definition of a symbol in the create_user method file_path = os.path.join("test_repo", "services.py") @@ -204,7 +204,7 @@ class TestLanguageServerSymbols: assert "services.py" in defining_symbol["location"]["uri"] @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_defining_symbol_imported_class(self, language_server: SyncLanguageServer) -> None: + def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for an imported class.""" # Test finding the definition of the 'User' class used in the UserService.create_user method file_path = os.path.join("test_repo", "services.py") @@ -216,7 +216,7 @@ class TestLanguageServerSymbols: assert defining_symbol.get("name") == "User" @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_defining_symbol_method_call(self, language_server: SyncLanguageServer) -> None: + def test_request_defining_symbol_method_call(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for a method call.""" # Create an example file path for a file that calls UserService.create_user examples_file_path = os.path.join("examples", "user_management.py") @@ -241,7 +241,7 @@ class TestLanguageServerSymbols: warnings.warn("Could not verify method call definition - file structure may differ from expected") @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_defining_symbol_none(self, language_server: SyncLanguageServer) -> None: + def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for a position with no symbol.""" # Test for a position with no symbol (e.g., whitespace or comment) file_path = os.path.join("test_repo", "services.py") @@ -252,7 +252,7 @@ class TestLanguageServerSymbols: assert defining_symbol is None @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_containing_symbol_variable(self, language_server: SyncLanguageServer) -> None: + def test_request_containing_symbol_variable(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol where the symbol is a variable.""" # Test for a position inside a variable definition file_path = os.path.join("test_repo", "services.py") @@ -265,7 +265,7 @@ class TestLanguageServerSymbols: assert containing_symbol["kind"] == SymbolKind.Variable @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_defining_symbol_nested_function(self, language_server: SyncLanguageServer) -> None: + def test_request_defining_symbol_nested_function(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for a nested function or closure.""" # Use the existing nested.py file which contains nested classes and methods file_path = os.path.join("test_repo", "nested.py") @@ -316,7 +316,7 @@ class TestLanguageServerSymbols: assert defining_symbol.get("kind") == SymbolKind.Function.value @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_symbol_methods_integration(self, language_server: SyncLanguageServer) -> None: + def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None: """Test the integration between different symbol-related methods.""" # This test demonstrates using the various symbol methods together # by finding a symbol and then checking its definition @@ -365,7 +365,7 @@ class TestLanguageServerSymbols: warnings.warn("Could not verify container hierarchy - implementation detail") @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_symbol_tree_structure(self, language_server: SyncLanguageServer) -> None: + def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None: """Test that the symbol tree structure is correctly built.""" # Get all symbols in the test file repo_structure = language_server.request_full_symbol_tree() @@ -393,7 +393,7 @@ class TestLanguageServerSymbols: assert user_management_roots == user_management_node["children"] @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_symbol_tree_structure_subdir(self, language_server: SyncLanguageServer) -> None: + def test_symbol_tree_structure_subdir(self, language_server: SolidLanguageServer) -> None: """Test that the symbol tree structure is correctly built.""" # Get all symbols in the test file examples_package_roots = language_server.request_full_symbol_tree(within_relative_path="examples") @@ -414,7 +414,7 @@ class TestLanguageServerSymbols: assert user_management_roots == user_management_node["children"] @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_dir_overview(self, language_server: SyncLanguageServer) -> None: + def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None: """Test that request_dir_overview returns correct symbol information for files in a directory.""" # Get overview of the examples directory overview = language_server.request_dir_overview("test_repo") @@ -439,7 +439,7 @@ class TestLanguageServerSymbols: assert symbol in services_symbols @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_request_document_overview(self, language_server: SyncLanguageServer) -> None: + def test_request_document_overview(self, language_server: SolidLanguageServer) -> None: """Test that request_document_overview returns correct symbol information for a file.""" # Get overview of the user_management.py file overview = language_server.request_document_overview(os.path.join("examples", "user_management.py")) @@ -449,7 +449,7 @@ class TestLanguageServerSymbols: assert {"UserStats", "UserManager", "process_user_data", "main"}.issubset(symbol_names) @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_containing_symbol_of_var_is_file(self, language_server: SyncLanguageServer) -> None: + def test_containing_symbol_of_var_is_file(self, language_server: SolidLanguageServer) -> None: """Test that the containing symbol of a variable is the file itself.""" # Get the containing symbol of a variable in a file file_path = os.path.join("test_repo", "services.py") diff --git a/test/multilspy/rust/test_rust_basic.py b/test/multilspy/rust/test_rust_basic.py index 02e9d502..d0576de2 100644 --- a/test/multilspy/rust/test_rust_basic.py +++ b/test/multilspy/rust/test_rust_basic.py @@ -2,15 +2,15 @@ import os import pytest -from multilspy import SyncLanguageServer -from multilspy.multilspy_config import Language -from multilspy.multilspy_utils import SymbolUtils +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language +from solidlsp.ls_utils import SymbolUtils @pytest.mark.rust class TestRustLanguageServer: @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True) - def test_find_references_raw(self, language_server: SyncLanguageServer) -> None: + def test_find_references_raw(self, language_server: SolidLanguageServer) -> None: # Directly test the request_references method for the add function file_path = os.path.join("src", "lib.rs") symbols = language_server.request_document_symbols(file_path) @@ -27,14 +27,14 @@ class TestRustLanguageServer: ), "main.rs should reference add (raw, tried all positions in selectionRange)" @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True) - def test_find_symbol(self, language_server: SyncLanguageServer) -> None: + def test_find_symbol(self, language_server: SolidLanguageServer) -> None: symbols = language_server.request_full_symbol_tree() assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree" # Add more as needed based on test_repo @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True) - def test_find_referencing_symbols(self, language_server: SyncLanguageServer) -> None: + def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: # Find references to 'add' defined in lib.rs, should be referenced from main.rs file_path = os.path.join("src", "lib.rs") symbols = language_server.request_document_symbols(file_path) @@ -51,7 +51,7 @@ class TestRustLanguageServer: ), "main.rs should reference add (tried all positions in selectionRange)" @pytest.mark.parametrize("language_server", [Language.RUST], indirect=True) - def test_overview_methods(self, language_server: SyncLanguageServer) -> None: + def test_overview_methods(self, language_server: SolidLanguageServer) -> None: symbols = language_server.request_full_symbol_tree() assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main missing from overview" assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add missing from overview" diff --git a/test/multilspy/typescript/test_typescript_basic.py b/test/multilspy/typescript/test_typescript_basic.py index 442b5a1e..1410c5c4 100644 --- a/test/multilspy/typescript/test_typescript_basic.py +++ b/test/multilspy/typescript/test_typescript_basic.py @@ -2,22 +2,22 @@ import os import pytest -from multilspy import SyncLanguageServer -from multilspy.multilspy_config import Language -from multilspy.multilspy_utils import SymbolUtils +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language +from solidlsp.ls_utils import SymbolUtils @pytest.mark.typescript class TestTypescriptLanguageServer: @pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True) - def test_find_symbol(self, language_server: SyncLanguageServer) -> None: + def test_find_symbol(self, language_server: SolidLanguageServer) -> None: symbols = language_server.request_full_symbol_tree() assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoClass"), "DemoClass not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "helperFunction"), "helperFunction not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "printValue"), "printValue method not found in symbol tree" @pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True) - def test_find_referencing_symbols(self, language_server: SyncLanguageServer) -> None: + def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: file_path = os.path.join("index.ts") symbols = language_server.request_document_symbols(file_path) helper_symbol = None diff --git a/test/serena/test_serena_agent.py b/test/serena/test_serena_agent.py index 853d86a1..4e297853 100644 --- a/test/serena/test_serena_agent.py +++ b/test/serena/test_serena_agent.py @@ -5,9 +5,9 @@ from dataclasses import dataclass import pytest -from multilspy.multilspy_config import Language from serena.agent import FindReferencingSymbolsTool, FindSymbolTool, Project, ProjectConfig, SerenaAgent, SerenaConfigBase from serena.process_isolated_agent import ProcessIsolatedSerenaAgent +from solidlsp.ls_config import Language from test.conftest import get_repo_path diff --git a/test/serena/test_symbol_editing.py b/test/serena/test_symbol_editing.py index 29976a51..652411ee 100644 --- a/test/serena/test_symbol_editing.py +++ b/test/serena/test_symbol_editing.py @@ -11,8 +11,8 @@ from typing import Literal import pytest -from multilspy.multilspy_config import Language from serena.symbol import CodeDiff +from solidlsp.ls_config import Language from src.serena.symbol import SymbolManager from test.conftest import create_ls, get_repo_path