From 605f8fba71cfd4c134ae568a45fdea5eb85241f0 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 23 Jun 2025 16:57:49 +0200 Subject: [PATCH] Remove multilspy package --- src/multilspy/.syncCommitId.remote | 1 - src/multilspy/.syncCommitId.this | 1 - src/multilspy/__init__.py | 8 - src/multilspy/language_server.py | 2311 ----------------- .../clangd_language_server.py | 190 -- .../initialize_params.json | 36 - .../runtime_dependencies.json | 29 - .../dart_language_server.py | 142 - .../initialize_params.json | 23 - .../runtime_dependencies.json | 45 - .../eclipse_jdtls/eclipse_jdtls.py | 422 --- .../eclipse_jdtls/initialize_params.json | 849 ------ .../eclipse_jdtls/runtime_dependencies.json | 72 - src/multilspy/language_servers/gopls/gopls.py | 151 -- .../gopls/initialize_params.json | 46 - .../intelephense/initialize_params.json | 36 - .../intelephense/intelephense.py | 200 -- .../intelephense/runtime_dependencies.json | 10 - .../initialize_params.json | 905 ------- .../jedi_language_server/jedi_server.py | 123 - .../initialize_params.json | 521 ---- .../kotlin_language_server.py | 226 -- .../runtime_dependencies.json | 41 - .../omnisharp/initialize_params.json | 631 ----- .../language_servers/omnisharp/omnisharp.py | 406 --- .../omnisharp/runtime_dependencies.json | 441 ---- .../workspace_did_change_configuration.json | 111 - .../pyright_language_server/pyright_server.py | 245 -- .../rust_analyzer/initialize_params.json | 917 ------- .../rust_analyzer/runtime_dependencies.json | 29 - .../rust_analyzer/rust_analyzer.py | 185 -- .../solargraph/initialize_params.json | 15 - .../solargraph/runtime_dependencies.json | 11 - .../language_servers/solargraph/solargraph.py | 187 -- .../initialize_params.json | 906 ------- .../runtime_dependencies.json | 16 - .../typescript_language_server.py | 245 -- 37 files changed, 10733 deletions(-) delete mode 100644 src/multilspy/.syncCommitId.remote delete mode 100644 src/multilspy/.syncCommitId.this delete mode 100644 src/multilspy/__init__.py delete mode 100644 src/multilspy/language_server.py delete mode 100644 src/multilspy/language_servers/clangd_language_server/clangd_language_server.py delete mode 100644 src/multilspy/language_servers/clangd_language_server/initialize_params.json delete mode 100644 src/multilspy/language_servers/clangd_language_server/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/dart_language_server/dart_language_server.py delete mode 100644 src/multilspy/language_servers/dart_language_server/initialize_params.json delete mode 100644 src/multilspy/language_servers/dart_language_server/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py delete mode 100644 src/multilspy/language_servers/eclipse_jdtls/initialize_params.json delete mode 100644 src/multilspy/language_servers/eclipse_jdtls/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/gopls/gopls.py delete mode 100644 src/multilspy/language_servers/gopls/initialize_params.json delete mode 100644 src/multilspy/language_servers/intelephense/initialize_params.json delete mode 100644 src/multilspy/language_servers/intelephense/intelephense.py delete mode 100644 src/multilspy/language_servers/intelephense/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/jedi_language_server/initialize_params.json delete mode 100644 src/multilspy/language_servers/jedi_language_server/jedi_server.py delete mode 100644 src/multilspy/language_servers/kotlin_language_server/initialize_params.json delete mode 100644 src/multilspy/language_servers/kotlin_language_server/kotlin_language_server.py delete mode 100644 src/multilspy/language_servers/kotlin_language_server/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/omnisharp/initialize_params.json delete mode 100644 src/multilspy/language_servers/omnisharp/omnisharp.py delete mode 100644 src/multilspy/language_servers/omnisharp/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/omnisharp/workspace_did_change_configuration.json delete mode 100644 src/multilspy/language_servers/pyright_language_server/pyright_server.py delete mode 100644 src/multilspy/language_servers/rust_analyzer/initialize_params.json delete mode 100644 src/multilspy/language_servers/rust_analyzer/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/rust_analyzer/rust_analyzer.py delete mode 100644 src/multilspy/language_servers/solargraph/initialize_params.json delete mode 100644 src/multilspy/language_servers/solargraph/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/solargraph/solargraph.py delete mode 100644 src/multilspy/language_servers/typescript_language_server/initialize_params.json delete mode 100644 src/multilspy/language_servers/typescript_language_server/runtime_dependencies.json delete mode 100644 src/multilspy/language_servers/typescript_language_server/typescript_language_server.py diff --git a/src/multilspy/.syncCommitId.remote b/src/multilspy/.syncCommitId.remote deleted file mode 100644 index 20a711e..0000000 --- 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 8467819..0000000 --- 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 0bd1758..0000000 --- a/src/multilspy/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -This module contains the multilspy API -""" - -from solidlsp 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 e6faf7a..0000000 --- a/src/multilspy/language_server.py +++ /dev/null @@ -1,2311 +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 -from typing import AsyncIterator, Dict, Iterator, List, Optional, Tuple, Union, cast - -import pathspec -import tqdm - -from solidlsp import multilspy_types -from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes, lsp_types -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, - LanguageServerHandler, - ProcessLaunchInfo, - StringDict, -) -from solidlsp.multilspy_config import Language, MultilspyConfig -from solidlsp.multilspy_exceptions import MultilspyException -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import FileUtils, PathUtils, TextUtils -from solidlsp.type_helpers import ensure_all_methods_implemented - -# 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 MatchedConsecutiveLines, search_files - - - -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 solidlsp.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 6960b9f..0000000 --- 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 solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_utils import FileUtils -from solidlsp.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 4330560..0000000 --- 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 53f5b7f..0000000 --- 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 8f9aaab..0000000 --- a/src/multilspy/language_servers/dart_language_server/dart_language_server.py +++ /dev/null @@ -1,142 +0,0 @@ -from contextlib import asynccontextmanager -import logging -import os -import pathlib -import stat -from typing import AsyncIterator -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -import json -from solidlsp.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 e90e408..0000000 --- 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 32e4929..0000000 --- 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 103c976..0000000 --- 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 solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_settings import MultilspySettings -from solidlsp.multilspy_utils import FileUtils -from solidlsp.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 b65b7b6..0000000 --- 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 1b00040..0000000 --- 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 117f5c2..0000000 --- a/src/multilspy/language_servers/gopls/gopls.py +++ /dev/null @@ -1,151 +0,0 @@ -import asyncio -import json -import logging -import os -import pathlib -import subprocess -from contextlib import asynccontextmanager -from typing import AsyncIterator - -from overrides import override - -from solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.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 40592e1..0000000 --- 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 4330560..0000000 --- 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 5a023fc..0000000 --- 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 solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.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 e3a6d7e..0000000 --- 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 5a04872..0000000 --- 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 6df8365..0000000 --- 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 - -from overrides import override - -from solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.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 a7b2ec0..0000000 --- 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 0b2906c..0000000 --- a/src/multilspy/language_servers/kotlin_language_server/kotlin_language_server.py +++ /dev/null @@ -1,226 +0,0 @@ -""" -Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin. -""" - -import dataclasses -import json -import logging -import os -import stat -import pathlib -from contextlib import asynccontextmanager -from typing import AsyncIterator - -from solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_utils import FileUtils -from solidlsp.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 3568c9d..0000000 --- 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 f9ee86c..0000000 --- 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 b376de7..0000000 --- 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 solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_exceptions import MultilspyException -from solidlsp.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 7dfa1fd..0000000 --- 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 148841c..0000000 --- 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 0cb9533..0000000 --- a/src/multilspy/language_servers/pyright_language_server/pyright_server.py +++ /dev/null @@ -1,245 +0,0 @@ -""" -Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python. -""" - -import asyncio -import logging -import os -import pathlib -import re -from contextlib import asynccontextmanager -from typing import AsyncIterator - -from overrides import override - -from solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.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 00368ee..0000000 --- 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 517d8b8..0000000 --- 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 60d7103..0000000 --- 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 solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_utils import FileUtils -from solidlsp.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 2cbb41f..0000000 --- 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 38cc237..0000000 --- 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 5f3d135..0000000 --- a/src/multilspy/language_servers/solargraph/solargraph.py +++ /dev/null @@ -1,187 +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 solidlsp.multilspy_logger import MultilspyLogger -from multilspy.language_server import LanguageServer -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.multilspy_config import MultilspyConfig - - -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 6209ba4..0000000 --- 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 90824a7..0000000 --- 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 edd3b30..0000000 --- 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 solidlsp.lsp_protocol_handler.lsp_types import InitializeParams -from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.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)