mirror of
https://github.com/tiennm99/serena.git
synced 2026-09-03 04:18:34 +00:00
Rename language server modules and classes
This commit is contained in:
+5
-5
@@ -52,9 +52,9 @@ from serena.util.inspection import determine_programming_language_composition, i
|
||||
from serena.util.shell import execute_shell_command
|
||||
from serena.util.thread import ExecutionResult, execute_with_timeout
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language, MultilspyConfig
|
||||
from solidlsp.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.multilspy_types import SymbolKind
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from serena.gui_log_viewer import GuiLogViewerHandler
|
||||
@@ -669,12 +669,12 @@ def create_ls_for_project(
|
||||
log.debug(f"Adding {len(spec.patterns)} patterns from {spec.file_path} to the ignored paths.")
|
||||
ignored_paths.extend(spec.patterns)
|
||||
log.debug(f"Using {len(ignored_paths)} ignored paths in total.")
|
||||
multilspy_config = MultilspyConfig(
|
||||
multilspy_config = LanguageServerConfig(
|
||||
code_language=project_instance.language,
|
||||
ignored_paths=ignored_paths,
|
||||
trace_lsp_communication=trace_lsp_communication,
|
||||
)
|
||||
ls_logger = MultilspyLogger(log_level=log_level)
|
||||
ls_logger = LanguageServerLogger(log_level=log_level)
|
||||
log.info(f"Creating language server instance for {project_instance.project_root}.")
|
||||
return SolidLanguageServer.create(
|
||||
multilspy_config,
|
||||
|
||||
@@ -11,7 +11,7 @@ from sensai.util.string import ToStringMixin
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls import ReferenceInSymbol as LSPReferenceInSymbol
|
||||
from solidlsp.multilspy_types import Position, SymbolKind, UnifiedSymbolInformation
|
||||
from solidlsp.ls_types import Position, SymbolKind, UnifiedSymbolInformation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .agent import SerenaAgent
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Generator
|
||||
from typing import TypeVar
|
||||
|
||||
from serena.util.file_system import find_all_non_ignored_files
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import threading
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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 FileUtils, PlatformUtils
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
|
||||
|
||||
class ClangdLanguageServer(SolidLanguageServer):
|
||||
@@ -24,7 +24,7 @@ class ClangdLanguageServer(SolidLanguageServer):
|
||||
Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a ClangdLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
@@ -41,7 +41,7 @@ class ClangdLanguageServer(SolidLanguageServer):
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
self.resolve_main_method_available = threading.Event()
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str:
|
||||
def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for ClangdLanguageServer.
|
||||
"""
|
||||
|
||||
@@ -6,8 +6,8 @@ import stat
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.multilspy_utils import FileUtils, PlatformUtils
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
|
||||
|
||||
class DartLanguageServer(SolidLanguageServer):
|
||||
@@ -28,7 +28,7 @@ class DartLanguageServer(SolidLanguageServer):
|
||||
"dart",
|
||||
)
|
||||
|
||||
def setup_runtime_dependencies(self, logger: "MultilspyLogger") -> str:
|
||||
def setup_runtime_dependencies(self, logger: "LanguageServerLogger") -> str:
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json")) as f:
|
||||
|
||||
@@ -18,10 +18,10 @@ from overrides import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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_settings import MultilspySettings
|
||||
from solidlsp.multilspy_utils import FileUtils, PlatformUtils
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -45,7 +45,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a new EclipseJDTLS instance initializing the language server settings appropriately.
|
||||
This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
@@ -56,7 +56,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
# ws_dir is the workspace directory for the EclipseJDTLS server
|
||||
ws_dir = str(
|
||||
PurePath(
|
||||
MultilspySettings.get_language_server_directory(),
|
||||
SolidLSPSettings.get_language_server_directory(),
|
||||
"EclipseJDTLS",
|
||||
"workspaces",
|
||||
uuid.uuid4().hex,
|
||||
@@ -64,7 +64,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
)
|
||||
|
||||
# shared_cache_location is the global cache used by Eclipse JDTLS across all workspaces
|
||||
shared_cache_location = str(PurePath(MultilspySettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex"))
|
||||
shared_cache_location = str(PurePath(SolidLSPSettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex"))
|
||||
|
||||
jre_path = self.runtime_dependency_paths.jre_path
|
||||
lombok_jar_path = self.runtime_dependency_paths.lombok_jar_path
|
||||
@@ -153,7 +153,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
"lib", # General
|
||||
]
|
||||
|
||||
def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> RuntimeDependencyPaths:
|
||||
def setupRuntimeDependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> RuntimeDependencyPaths:
|
||||
"""
|
||||
Setup runtime dependencies for EclipseJDTLS.
|
||||
"""
|
||||
|
||||
@@ -10,8 +10,8 @@ from overrides import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
|
||||
|
||||
class Gopls(SolidLanguageServer):
|
||||
@@ -71,7 +71,7 @@ class Gopls(SolidLanguageServer):
|
||||
|
||||
return True
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
self.setup_runtime_dependency()
|
||||
|
||||
super().__init__(
|
||||
|
||||
@@ -15,9 +15,9 @@ from overrides import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.multilspy_config import MultilspyConfig
|
||||
from solidlsp.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.multilspy_utils import PlatformId, PlatformUtils
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import PlatformId, PlatformUtils
|
||||
|
||||
|
||||
class Intelephense(SolidLanguageServer):
|
||||
@@ -33,7 +33,7 @@ class Intelephense(SolidLanguageServer):
|
||||
# - 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:
|
||||
def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Intelephense.
|
||||
"""
|
||||
@@ -97,7 +97,7 @@ class Intelephense(SolidLanguageServer):
|
||||
|
||||
return f"{intelephense_executable_path} --stdio"
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
# Setup runtime dependencies before initializing
|
||||
intelephense_cmd = self.setup_runtime_dependencies(logger, config)
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ from overrides import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
|
||||
|
||||
class JediServer(SolidLanguageServer):
|
||||
@@ -21,7 +21,7 @@ class JediServer(SolidLanguageServer):
|
||||
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a JediServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
|
||||
@@ -12,9 +12,9 @@ import stat
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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 FileUtils, PlatformUtils
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -33,7 +33,7 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a Kotlin Language Server instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
@@ -54,7 +54,7 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
"kotlin",
|
||||
)
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> KotlinRuntimeDependencyPaths:
|
||||
def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> KotlinRuntimeDependencyPaths:
|
||||
"""
|
||||
Setup runtime dependencies for Kotlin Language Server.
|
||||
"""
|
||||
|
||||
@@ -15,10 +15,10 @@ from overrides import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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_exceptions import MultilspyException
|
||||
from solidlsp.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.multilspy_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils
|
||||
|
||||
|
||||
def breadth_first_file_scan(root) -> Iterable[str]:
|
||||
@@ -59,7 +59,7 @@ class OmniSharp(SolidLanguageServer):
|
||||
Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates an OmniSharp instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
@@ -68,7 +68,7 @@ class OmniSharp(SolidLanguageServer):
|
||||
slnfilename = find_least_depth_sln_file(repository_root_path)
|
||||
if slnfilename is None:
|
||||
logger.log("No *.sln file found in repository", logging.ERROR)
|
||||
raise MultilspyException("No SLN file found in repository")
|
||||
raise LanguageServerException("No SLN file found in repository")
|
||||
|
||||
cmd = " ".join(
|
||||
[
|
||||
@@ -136,7 +136,7 @@ class OmniSharp(SolidLanguageServer):
|
||||
|
||||
return d
|
||||
|
||||
def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> tuple[str, str]:
|
||||
def setupRuntimeDependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> tuple[str, str]:
|
||||
"""
|
||||
Setup runtime dependencies for OmniSharp.
|
||||
"""
|
||||
|
||||
@@ -13,8 +13,8 @@ from overrides import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
|
||||
|
||||
class PyrightServer(SolidLanguageServer):
|
||||
@@ -23,7 +23,7 @@ class PyrightServer(SolidLanguageServer):
|
||||
Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a PyrightServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
|
||||
@@ -14,9 +14,9 @@ from overrides import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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 FileUtils, PlatformUtils
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
|
||||
|
||||
class RustAnalyzer(SolidLanguageServer):
|
||||
@@ -24,7 +24,7 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
@@ -45,7 +45,7 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["target"]
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str:
|
||||
def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for rust_analyzer.
|
||||
"""
|
||||
|
||||
@@ -15,8 +15,8 @@ from typing import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
|
||||
|
||||
class Solargraph(SolidLanguageServer):
|
||||
@@ -25,7 +25,7 @@ class Solargraph(SolidLanguageServer):
|
||||
Contains various configurations and settings specific to Ruby.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a Solargraph instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
@@ -47,7 +47,7 @@ class Solargraph(SolidLanguageServer):
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["vendor"]
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig, repository_root_path: str) -> str:
|
||||
def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig, repository_root_path: str) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Solargraph.
|
||||
"""
|
||||
|
||||
+5
-5
@@ -16,9 +16,9 @@ from overrides import override
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
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
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import PlatformId, PlatformUtils
|
||||
|
||||
# Platform-specific imports
|
||||
if os.name != "nt": # Unix-like systems
|
||||
@@ -41,7 +41,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
@@ -65,7 +65,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
"coverage",
|
||||
]
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str:
|
||||
def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for TypeScript Language Server.
|
||||
"""
|
||||
|
||||
+86
-86
@@ -19,7 +19,7 @@ import pathspec
|
||||
import tqdm
|
||||
|
||||
from serena.text_utils import MatchedConsecutiveLines, search_files
|
||||
from solidlsp import multilspy_types
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.ls_handler import SolidLanguageServerHandler
|
||||
from solidlsp.lsp_protocol_handler import lsp_types
|
||||
from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes
|
||||
@@ -30,19 +30,19 @@ from solidlsp.lsp_protocol_handler.server import (
|
||||
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.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PathUtils, TextUtils
|
||||
|
||||
GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, multilspy_types.UnifiedSymbolInformation]
|
||||
GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, ls_types.UnifiedSymbolInformation]
|
||||
|
||||
|
||||
@dataclasses.dataclass(kw_only=True)
|
||||
class ReferenceInSymbol:
|
||||
"""A symbol retrieved when requesting reference to a symbol, together with the location of the reference"""
|
||||
|
||||
symbol: multilspy_types.UnifiedSymbolInformation
|
||||
symbol: ls_types.UnifiedSymbolInformation
|
||||
line: int
|
||||
character: int
|
||||
|
||||
@@ -90,7 +90,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str, timeout: float | None = None
|
||||
cls, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, timeout: float | None = None
|
||||
) -> "SolidLanguageServer":
|
||||
"""
|
||||
Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language.
|
||||
@@ -178,12 +178,12 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
else:
|
||||
logger.log(f"Language {config.code_language} is not supported", logging.ERROR)
|
||||
raise MultilspyException(f"Language {config.code_language} is not supported")
|
||||
raise LanguageServerException(f"Language {config.code_language} is not supported")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: MultilspyConfig,
|
||||
logger: MultilspyLogger,
|
||||
config: LanguageServerConfig,
|
||||
logger: LanguageServerLogger,
|
||||
repository_root_path: str,
|
||||
process_launch_info: ProcessLaunchInfo,
|
||||
language_id: str,
|
||||
@@ -214,7 +214,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
# 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]]]
|
||||
str, tuple[str, tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]]
|
||||
] = {}
|
||||
"""Maps file paths to a tuple of (file_content_hash, result_of_request_document_symbols)"""
|
||||
self._cache_lock = threading.Lock()
|
||||
@@ -368,7 +368,7 @@ class SolidLanguageServer(ABC):
|
||||
"open_file called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
raise LanguageServerException("Language Server not started")
|
||||
|
||||
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
|
||||
uri = pathlib.Path(absolute_file_path).as_uri()
|
||||
@@ -411,7 +411,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
def insert_text_at_position(
|
||||
self, relative_file_path: str, line: int, column: int, text_to_be_inserted: str
|
||||
) -> multilspy_types.Position:
|
||||
) -> ls_types.Position:
|
||||
"""
|
||||
Insert text at the given line and column in the given file and return
|
||||
the updated cursor position after inserting the text.
|
||||
@@ -426,7 +426,7 @@ class SolidLanguageServer(ABC):
|
||||
"insert_text_at_position called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
raise LanguageServerException("Language Server not started")
|
||||
|
||||
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
|
||||
uri = pathlib.Path(absolute_file_path).as_uri()
|
||||
@@ -456,13 +456,13 @@ class SolidLanguageServer(ABC):
|
||||
],
|
||||
}
|
||||
)
|
||||
return multilspy_types.Position(line=new_l, character=new_c)
|
||||
return ls_types.Position(line=new_l, character=new_c)
|
||||
|
||||
def delete_text_between_positions(
|
||||
self,
|
||||
relative_file_path: str,
|
||||
start: multilspy_types.Position,
|
||||
end: multilspy_types.Position,
|
||||
start: ls_types.Position,
|
||||
end: ls_types.Position,
|
||||
) -> str:
|
||||
"""
|
||||
Delete text between the given start and end positions in the given file and return the deleted text.
|
||||
@@ -472,7 +472,7 @@ class SolidLanguageServer(ABC):
|
||||
"insert_text_at_position called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
raise LanguageServerException("Language Server not started")
|
||||
|
||||
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
|
||||
uri = pathlib.Path(absolute_file_path).as_uri()
|
||||
@@ -500,7 +500,7 @@ class SolidLanguageServer(ABC):
|
||||
def _send_definition_request(self, definition_params: DefinitionParams) -> Definition | list[LocationLink] | None:
|
||||
return self.server.send.definition(definition_params)
|
||||
|
||||
def request_definition(self, relative_file_path: str, line: int, column: int) -> list[multilspy_types.Location]:
|
||||
def request_definition(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]:
|
||||
"""
|
||||
Raise a [textDocument/definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) request to the Language Server
|
||||
for the symbol at the given line and column in the given file. Wait for the response and return the result.
|
||||
@@ -516,7 +516,7 @@ class SolidLanguageServer(ABC):
|
||||
"find_function_definition called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
raise LanguageServerException("Language Server not started")
|
||||
|
||||
with self.open_file(relative_file_path):
|
||||
# sending request to the language server and waiting for response
|
||||
@@ -534,29 +534,29 @@ class SolidLanguageServer(ABC):
|
||||
)
|
||||
response = self._send_definition_request(definition_params)
|
||||
|
||||
ret: list[multilspy_types.Location] = []
|
||||
ret: list[ls_types.Location] = []
|
||||
if isinstance(response, list):
|
||||
# response is either of type Location[] or LocationLink[]
|
||||
for item in response:
|
||||
assert isinstance(item, dict)
|
||||
if LSPConstants.URI in item and LSPConstants.RANGE in item:
|
||||
new_item: multilspy_types.Location = {}
|
||||
new_item: ls_types.Location = {}
|
||||
new_item.update(item)
|
||||
new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"])
|
||||
new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path)
|
||||
ret.append(multilspy_types.Location(new_item))
|
||||
ret.append(ls_types.Location(new_item))
|
||||
elif (
|
||||
LSPConstants.ORIGIN_SELECTION_RANGE in item
|
||||
and LSPConstants.TARGET_URI in item
|
||||
and LSPConstants.TARGET_RANGE in item
|
||||
and LSPConstants.TARGET_SELECTION_RANGE in item
|
||||
):
|
||||
new_item: multilspy_types.Location = {}
|
||||
new_item: ls_types.Location = {}
|
||||
new_item["uri"] = item[LSPConstants.TARGET_URI]
|
||||
new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"])
|
||||
new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path)
|
||||
new_item["range"] = item[LSPConstants.TARGET_SELECTION_RANGE]
|
||||
ret.append(multilspy_types.Location(**new_item))
|
||||
ret.append(ls_types.Location(**new_item))
|
||||
else:
|
||||
assert False, f"Unexpected response from Language Server: {item}"
|
||||
elif isinstance(response, dict):
|
||||
@@ -564,11 +564,11 @@ class SolidLanguageServer(ABC):
|
||||
assert LSPConstants.URI in response
|
||||
assert LSPConstants.RANGE in response
|
||||
|
||||
new_item: multilspy_types.Location = {}
|
||||
new_item: ls_types.Location = {}
|
||||
new_item.update(response)
|
||||
new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"])
|
||||
new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path)
|
||||
ret.append(multilspy_types.Location(**new_item))
|
||||
ret.append(ls_types.Location(**new_item))
|
||||
elif response is None:
|
||||
# Some language servers return None when they cannot find a definition
|
||||
# This is expected for certain symbol types like generics or types with incomplete information
|
||||
@@ -591,7 +591,7 @@ class SolidLanguageServer(ABC):
|
||||
}
|
||||
)
|
||||
|
||||
def request_references(self, relative_file_path: str, line: int, column: int) -> list[multilspy_types.Location]:
|
||||
def request_references(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]:
|
||||
"""
|
||||
Raise a [textDocument/references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) request to the Language Server
|
||||
to find references to the symbol at the given line and column in the given file. Wait for the response and return the result.
|
||||
@@ -608,7 +608,7 @@ class SolidLanguageServer(ABC):
|
||||
"request_references called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
raise LanguageServerException("Language Server not started")
|
||||
|
||||
with self.open_file(relative_file_path):
|
||||
try:
|
||||
@@ -624,7 +624,7 @@ class SolidLanguageServer(ABC):
|
||||
if response is None:
|
||||
return []
|
||||
|
||||
ret: list[multilspy_types.Location] = []
|
||||
ret: list[ls_types.Location] = []
|
||||
assert isinstance(response, list), f"Unexpected response from Language Server (expected list, got {type(response)}): {response}"
|
||||
for item in response:
|
||||
assert isinstance(item, dict), f"Unexpected response from Language Server (expected dict, got {type(item)}): {item}"
|
||||
@@ -637,11 +637,11 @@ class SolidLanguageServer(ABC):
|
||||
self.logger.log(f"Ignoring reference in {rel_path} since it should be ignored", logging.DEBUG)
|
||||
continue
|
||||
|
||||
new_item: multilspy_types.Location = {}
|
||||
new_item: ls_types.Location = {}
|
||||
new_item.update(item)
|
||||
new_item["absolutePath"] = str(abs_path)
|
||||
new_item["relativePath"] = str(rel_path)
|
||||
ret.append(multilspy_types.Location(**new_item))
|
||||
ret.append(ls_types.Location(**new_item))
|
||||
|
||||
return ret
|
||||
|
||||
@@ -697,7 +697,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
def request_completions(
|
||||
self, relative_file_path: str, line: int, column: int, allow_incomplete: bool = False
|
||||
) -> list[multilspy_types.CompletionItem]:
|
||||
) -> list[ls_types.CompletionItem]:
|
||||
"""
|
||||
Raise a [textDocument/completion](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion) request to the Language Server
|
||||
to find completions at the given line and column in the given file. Wait for the response and return the result.
|
||||
@@ -737,7 +737,7 @@ class SolidLanguageServer(ABC):
|
||||
# TODO: Handle the case when the completion is a keyword
|
||||
items = [item for item in response if item["kind"] != LSPTypes.CompletionItemKind.Keyword]
|
||||
|
||||
completions_list: list[multilspy_types.CompletionItem] = []
|
||||
completions_list: list[ls_types.CompletionItem] = []
|
||||
|
||||
for item in items:
|
||||
assert "insertText" in item or "textEdit" in item
|
||||
@@ -776,14 +776,14 @@ class SolidLanguageServer(ABC):
|
||||
else:
|
||||
assert False
|
||||
|
||||
completion_item = multilspy_types.CompletionItem(**completion_item)
|
||||
completion_item = ls_types.CompletionItem(**completion_item)
|
||||
completions_list.append(completion_item)
|
||||
|
||||
return [json.loads(json_repr) for json_repr in set(json.dumps(item, sort_keys=True) for item in completions_list)]
|
||||
|
||||
def request_document_symbols(
|
||||
self, relative_file_path: str, include_body: bool = False
|
||||
) -> tuple[list[multilspy_types.UnifiedSymbolInformation], list[multilspy_types.UnifiedSymbolInformation]]:
|
||||
) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]:
|
||||
"""
|
||||
Raise a [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol) request to the Language Server
|
||||
to find symbols in the given file. Wait for the response and return the result.
|
||||
@@ -823,14 +823,14 @@ class SolidLanguageServer(ABC):
|
||||
)
|
||||
|
||||
def turn_item_into_symbol_with_children(item: GenericDocumentSymbol):
|
||||
item = cast(multilspy_types.UnifiedSymbolInformation, item)
|
||||
item = cast(ls_types.UnifiedSymbolInformation, item)
|
||||
absolute_path = os.path.join(self.repository_root_path, relative_file_path)
|
||||
|
||||
# handle missing entries in location
|
||||
if "location" not in item:
|
||||
uri = pathlib.Path(absolute_path).as_uri()
|
||||
assert "range" in item
|
||||
tree_location = multilspy_types.Location(
|
||||
tree_location = ls_types.Location(
|
||||
uri=uri,
|
||||
range=item["range"],
|
||||
absolutePath=absolute_path,
|
||||
@@ -855,9 +855,9 @@ class SolidLanguageServer(ABC):
|
||||
child["parent"] = item
|
||||
item[LSPConstants.CHILDREN] = children
|
||||
|
||||
flat_all_symbol_list: list[multilspy_types.UnifiedSymbolInformation] = []
|
||||
flat_all_symbol_list: list[ls_types.UnifiedSymbolInformation] = []
|
||||
assert isinstance(response, list), f"Unexpected response from Language Server: {response}"
|
||||
root_nodes: list[multilspy_types.UnifiedSymbolInformation] = []
|
||||
root_nodes: list[ls_types.UnifiedSymbolInformation] = []
|
||||
for root_item in response:
|
||||
if "range" not in root_item and "location" not in root_item:
|
||||
if root_item["kind"] in [SymbolKind.File, SymbolKind.Module]:
|
||||
@@ -867,7 +867,7 @@ class SolidLanguageServer(ABC):
|
||||
# so we cast and rename the var after the mutating call to turn_item_into_symbol_with_children
|
||||
# which turned and item into a "symbol"
|
||||
turn_item_into_symbol_with_children(root_item)
|
||||
root_symbol = cast(multilspy_types.UnifiedSymbolInformation, root_item)
|
||||
root_symbol = cast(ls_types.UnifiedSymbolInformation, root_item)
|
||||
root_symbol["parent"] = None
|
||||
|
||||
root_nodes.append(root_symbol)
|
||||
@@ -878,9 +878,9 @@ class SolidLanguageServer(ABC):
|
||||
if LSPConstants.CHILDREN in root_symbol:
|
||||
# TODO: l_tree should be a list of TreeRepr. Define the following function to return TreeRepr as well
|
||||
|
||||
def visit_tree_nodes_and_build_tree_repr(node: GenericDocumentSymbol) -> list[multilspy_types.UnifiedSymbolInformation]:
|
||||
node = cast(multilspy_types.UnifiedSymbolInformation, node)
|
||||
l: list[multilspy_types.UnifiedSymbolInformation] = []
|
||||
def visit_tree_nodes_and_build_tree_repr(node: GenericDocumentSymbol) -> list[ls_types.UnifiedSymbolInformation]:
|
||||
node = cast(ls_types.UnifiedSymbolInformation, node)
|
||||
l: list[ls_types.UnifiedSymbolInformation] = []
|
||||
turn_item_into_symbol_with_children(node)
|
||||
assert LSPConstants.CHILDREN in node
|
||||
children = node[LSPConstants.CHILDREN]
|
||||
@@ -891,7 +891,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
flat_all_symbol_list.extend(visit_tree_nodes_and_build_tree_repr(root_symbol))
|
||||
else:
|
||||
flat_all_symbol_list.append(multilspy_types.UnifiedSymbolInformation(**root_symbol))
|
||||
flat_all_symbol_list.append(ls_types.UnifiedSymbolInformation(**root_symbol))
|
||||
|
||||
result = flat_all_symbol_list, root_nodes
|
||||
self.logger.log(f"Caching document symbols for {relative_file_path}", logging.DEBUG)
|
||||
@@ -902,7 +902,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
def request_full_symbol_tree(
|
||||
self, within_relative_path: str | None = None, include_body: bool = False
|
||||
) -> list[multilspy_types.UnifiedSymbolInformation]:
|
||||
) -> list[ls_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.
|
||||
@@ -937,7 +937,7 @@ class SolidLanguageServer(ABC):
|
||||
return root_nodes
|
||||
|
||||
# Helper function to recursively process directories
|
||||
def process_directory(rel_dir_path: str) -> list[multilspy_types.UnifiedSymbolInformation]:
|
||||
def process_directory(rel_dir_path: str) -> list[ls_types.UnifiedSymbolInformation]:
|
||||
abs_dir_path = self.repository_root_path if rel_dir_path == "." else os.path.join(self.repository_root_path, rel_dir_path)
|
||||
abs_dir_path = os.path.realpath(abs_dir_path)
|
||||
|
||||
@@ -952,10 +952,10 @@ class SolidLanguageServer(ABC):
|
||||
return []
|
||||
|
||||
# Create package symbol for directory
|
||||
package_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore
|
||||
package_symbol = ls_types.UnifiedSymbolInformation( # type: ignore
|
||||
name=os.path.basename(abs_dir_path),
|
||||
kind=multilspy_types.SymbolKind.Package,
|
||||
location=multilspy_types.Location(
|
||||
kind=ls_types.SymbolKind.Package,
|
||||
location=ls_types.Location(
|
||||
uri=str(pathlib.Path(abs_dir_path).as_uri()),
|
||||
range={"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}},
|
||||
absolutePath=str(abs_dir_path),
|
||||
@@ -985,12 +985,12 @@ class SolidLanguageServer(ABC):
|
||||
file_rel_path = str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path))
|
||||
with self.open_file(file_rel_path) as file_data:
|
||||
fileRange = self._get_range_from_file_content(file_data.contents)
|
||||
file_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore
|
||||
file_symbol = ls_types.UnifiedSymbolInformation( # type: ignore
|
||||
name=os.path.splitext(contained_dir_or_file_name)[0],
|
||||
kind=multilspy_types.SymbolKind.File,
|
||||
kind=ls_types.SymbolKind.File,
|
||||
range=fileRange,
|
||||
selectionRange=fileRange,
|
||||
location=multilspy_types.Location(
|
||||
location=ls_types.Location(
|
||||
uri=str(pathlib.Path(contained_dir_or_file_abs_path).as_uri()),
|
||||
range=fileRange,
|
||||
absolutePath=str(contained_dir_or_file_abs_path),
|
||||
@@ -1006,7 +1006,7 @@ class SolidLanguageServer(ABC):
|
||||
package_symbol["children"].append(file_symbol)
|
||||
|
||||
# TODO: Not sure if this is actually still needed given recent changes to relative path handling
|
||||
def fix_relative_path(nodes: list[multilspy_types.UnifiedSymbolInformation]):
|
||||
def fix_relative_path(nodes: list[ls_types.UnifiedSymbolInformation]):
|
||||
for node in nodes:
|
||||
if "location" in node and "relativePath" in node["location"]:
|
||||
path = Path(node["location"]["relativePath"])
|
||||
@@ -1028,18 +1028,18 @@ class SolidLanguageServer(ABC):
|
||||
return process_directory(start_rel_path)
|
||||
|
||||
@staticmethod
|
||||
def _get_range_from_file_content(file_content: str) -> multilspy_types.Range:
|
||||
def _get_range_from_file_content(file_content: str) -> ls_types.Range:
|
||||
"""
|
||||
Get the range for the given file.
|
||||
"""
|
||||
lines = file_content.split("\n")
|
||||
end_line = len(lines)
|
||||
end_column = len(lines[-1])
|
||||
return multilspy_types.Range(
|
||||
start=multilspy_types.Position(line=0, character=0), end=multilspy_types.Position(line=end_line, character=end_column)
|
||||
return ls_types.Range(
|
||||
start=ls_types.Position(line=0, character=0), end=ls_types.Position(line=end_line, character=end_column)
|
||||
)
|
||||
|
||||
def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]:
|
||||
def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]]:
|
||||
"""
|
||||
An overview of the given directory.
|
||||
|
||||
@@ -1048,11 +1048,11 @@ class SolidLanguageServer(ABC):
|
||||
"""
|
||||
symbol_tree = self.request_full_symbol_tree(relative_dir_path)
|
||||
# Initialize result dictionary
|
||||
result: dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]] = defaultdict(list)
|
||||
result: dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]] = defaultdict(list)
|
||||
|
||||
# Helper function to process a symbol and its children
|
||||
def process_symbol(symbol: multilspy_types.UnifiedSymbolInformation):
|
||||
if symbol["kind"] == multilspy_types.SymbolKind.File:
|
||||
def process_symbol(symbol: ls_types.UnifiedSymbolInformation):
|
||||
if symbol["kind"] == ls_types.SymbolKind.File:
|
||||
# For file symbols, process their children (top-level symbols)
|
||||
for child in symbol["children"]:
|
||||
assert "location" in child
|
||||
@@ -1075,7 +1075,7 @@ class SolidLanguageServer(ABC):
|
||||
process_symbol(root)
|
||||
return result
|
||||
|
||||
def request_document_overview(self, relative_file_path: str) -> list[tuple[str, multilspy_types.SymbolKind, int, int]]:
|
||||
def request_document_overview(self, relative_file_path: str) -> list[tuple[str, ls_types.SymbolKind, int, int]]:
|
||||
"""
|
||||
An overview of the given file.
|
||||
Returns the list of tuples (name, kind, line, column) of all top-level symbols in the file.
|
||||
@@ -1091,7 +1091,7 @@ class SolidLanguageServer(ABC):
|
||||
raise KeyError(f"Could not process symbol of name {root.get('name', 'unknown')} in {relative_file_path=}") from e
|
||||
return result
|
||||
|
||||
def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]:
|
||||
def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]]:
|
||||
"""
|
||||
An overview of all symbols in the given file or directory.
|
||||
|
||||
@@ -1108,7 +1108,7 @@ class SolidLanguageServer(ABC):
|
||||
else:
|
||||
return self.request_dir_overview(within_relative_path)
|
||||
|
||||
def request_hover(self, relative_file_path: str, line: int, column: int) -> multilspy_types.Hover | None:
|
||||
def request_hover(self, relative_file_path: str, line: int, column: int) -> ls_types.Hover | None:
|
||||
"""
|
||||
Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server
|
||||
to find the hover information at the given line and column in the given file. Wait for the response and return the result.
|
||||
@@ -1135,12 +1135,12 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
assert isinstance(response, dict)
|
||||
|
||||
return multilspy_types.Hover(**response)
|
||||
return ls_types.Hover(**response)
|
||||
|
||||
# ----------------------------- FROM HERE ON MODIFICATIONS BY MISCHA --------------------
|
||||
|
||||
def retrieve_symbol_body(
|
||||
self, symbol: multilspy_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation
|
||||
self, symbol: ls_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation
|
||||
) -> str:
|
||||
"""
|
||||
Load the body of the given symbol. If the body is already contained in the symbol, just return it.
|
||||
@@ -1169,7 +1169,7 @@ class SolidLanguageServer(ABC):
|
||||
"request_parsed_files called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
raise LanguageServerException("Language Server not started")
|
||||
rel_file_paths = []
|
||||
for root, dirs, files in os.walk(self.repository_root_path):
|
||||
dirs[:] = [d for d in dirs if not self.is_ignored_path(os.path.join(root, d))]
|
||||
@@ -1244,7 +1244,7 @@ class SolidLanguageServer(ABC):
|
||||
"request_referencing_symbols called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
raise LanguageServerException("Language Server not started")
|
||||
|
||||
# First, get all references to the symbol
|
||||
references = self.request_references(relative_file_path, line, column)
|
||||
@@ -1281,7 +1281,7 @@ class SolidLanguageServer(ABC):
|
||||
containing_symbol_name = ref_text.split(".")[0]
|
||||
all_symbols, _ = self.request_document_symbols(ref_path)
|
||||
for symbol in all_symbols:
|
||||
if symbol["name"] == containing_symbol_name and symbol["kind"] == multilspy_types.SymbolKind.Variable:
|
||||
if symbol["name"] == containing_symbol_name and symbol["kind"] == ls_types.SymbolKind.Variable:
|
||||
containing_symbol = copy(symbol)
|
||||
containing_symbol["location"] = ref
|
||||
containing_symbol["range"] = ref["range"]
|
||||
@@ -1294,7 +1294,7 @@ class SolidLanguageServer(ABC):
|
||||
logging.WARNING,
|
||||
)
|
||||
fileRange = self._get_range_from_file_content(file_data.contents)
|
||||
location = multilspy_types.Location(
|
||||
location = ls_types.Location(
|
||||
uri=str(pathlib.Path(os.path.join(self.repository_root_path, ref_path)).as_uri()),
|
||||
range=fileRange,
|
||||
absolutePath=str(os.path.join(self.repository_root_path, ref_path)),
|
||||
@@ -1307,8 +1307,8 @@ class SolidLanguageServer(ABC):
|
||||
else:
|
||||
body = ""
|
||||
|
||||
containing_symbol = multilspy_types.UnifiedSymbolInformation(
|
||||
kind=multilspy_types.SymbolKind.File,
|
||||
containing_symbol = ls_types.UnifiedSymbolInformation(
|
||||
kind=ls_types.SymbolKind.File,
|
||||
range=fileRange,
|
||||
selectionRange=fileRange,
|
||||
location=location,
|
||||
@@ -1316,7 +1316,7 @@ class SolidLanguageServer(ABC):
|
||||
children=[],
|
||||
body=body,
|
||||
)
|
||||
if containing_symbol is None or (not include_file_symbols and containing_symbol["kind"] == multilspy_types.SymbolKind.File):
|
||||
if containing_symbol is None or (not include_file_symbols and containing_symbol["kind"] == ls_types.SymbolKind.File):
|
||||
continue
|
||||
|
||||
assert "location" in containing_symbol
|
||||
@@ -1363,7 +1363,7 @@ class SolidLanguageServer(ABC):
|
||||
column: int | None = None,
|
||||
strict: bool = False,
|
||||
include_body: bool = False,
|
||||
) -> multilspy_types.UnifiedSymbolInformation | None:
|
||||
) -> ls_types.UnifiedSymbolInformation | None:
|
||||
"""
|
||||
Finds the first symbol containing the position for the given file.
|
||||
For Python, container symbols are considered to be those with kinds corresponding to
|
||||
@@ -1409,7 +1409,7 @@ class SolidLanguageServer(ABC):
|
||||
for symbol in symbols:
|
||||
if "location" not in symbol:
|
||||
range = symbol["range"]
|
||||
location = multilspy_types.Location(
|
||||
location = ls_types.Location(
|
||||
uri=f"file:/{absolute_file_path}",
|
||||
range=range,
|
||||
absolutePath=absolute_file_path,
|
||||
@@ -1424,9 +1424,9 @@ class SolidLanguageServer(ABC):
|
||||
location["uri"] = Path(absolute_file_path).as_uri()
|
||||
|
||||
# Allowed container kinds, currently only for Python
|
||||
container_symbol_kinds = {multilspy_types.SymbolKind.Method, multilspy_types.SymbolKind.Function, multilspy_types.SymbolKind.Class}
|
||||
container_symbol_kinds = {ls_types.SymbolKind.Method, ls_types.SymbolKind.Function, ls_types.SymbolKind.Class}
|
||||
|
||||
def is_position_in_range(line: int, range_d: multilspy_types.Range) -> bool:
|
||||
def is_position_in_range(line: int, range_d: ls_types.Range) -> bool:
|
||||
start = range_d["start"]
|
||||
end = range_d["end"]
|
||||
|
||||
@@ -1447,7 +1447,7 @@ class SolidLanguageServer(ABC):
|
||||
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]
|
||||
var_containers = [s for s in symbols if s["kind"] == ls_types.SymbolKind.Variable]
|
||||
candidate_containers.extend(var_containers)
|
||||
|
||||
if not candidate_containers:
|
||||
@@ -1471,8 +1471,8 @@ class SolidLanguageServer(ABC):
|
||||
return None
|
||||
|
||||
def request_container_of_symbol(
|
||||
self, symbol: multilspy_types.UnifiedSymbolInformation, include_body: bool = False
|
||||
) -> multilspy_types.UnifiedSymbolInformation | None:
|
||||
self, symbol: ls_types.UnifiedSymbolInformation, include_body: bool = False
|
||||
) -> ls_types.UnifiedSymbolInformation | None:
|
||||
"""
|
||||
Finds the container of the given symbol if there is one. If the parent attribute is present, the parent is returned
|
||||
without further searching.
|
||||
@@ -1498,7 +1498,7 @@ class SolidLanguageServer(ABC):
|
||||
line: int,
|
||||
column: int,
|
||||
include_body: bool = False,
|
||||
) -> multilspy_types.UnifiedSymbolInformation | None:
|
||||
) -> ls_types.UnifiedSymbolInformation | None:
|
||||
"""
|
||||
Finds the symbol that defines the symbol at the given location.
|
||||
|
||||
@@ -1516,7 +1516,7 @@ class SolidLanguageServer(ABC):
|
||||
"request_defining_symbol called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise MultilspyException("Language Server not started")
|
||||
raise LanguageServerException("Language Server not started")
|
||||
|
||||
# Get the definition location(s)
|
||||
definitions = self.request_definition(relative_file_path, line, column)
|
||||
@@ -1596,7 +1596,7 @@ class SolidLanguageServer(ABC):
|
||||
logging.ERROR,
|
||||
)
|
||||
|
||||
def request_workspace_symbol(self, query: str) -> list[multilspy_types.UnifiedSymbolInformation] | None:
|
||||
def request_workspace_symbol(self, query: str) -> list[ls_types.UnifiedSymbolInformation] | None:
|
||||
"""
|
||||
Raise a [workspace/symbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) request to the Language Server
|
||||
to find symbols across the whole workspace. Wait for the response and return the result.
|
||||
@@ -1611,7 +1611,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
assert isinstance(response, list)
|
||||
|
||||
ret: list[multilspy_types.UnifiedSymbolInformation] = []
|
||||
ret: list[ls_types.UnifiedSymbolInformation] = []
|
||||
for item in response:
|
||||
assert isinstance(item, dict)
|
||||
|
||||
@@ -1619,7 +1619,7 @@ class SolidLanguageServer(ABC):
|
||||
assert LSPConstants.KIND in item
|
||||
assert LSPConstants.LOCATION in item
|
||||
|
||||
ret.append(multilspy_types.UnifiedSymbolInformation(**item))
|
||||
ret.append(ls_types.UnifiedSymbolInformation(**item))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ class Language(str, Enum):
|
||||
|
||||
|
||||
@dataclass
|
||||
class MultilspyConfig:
|
||||
class LanguageServerConfig:
|
||||
"""
|
||||
Configuration parameters
|
||||
"""
|
||||
@@ -3,7 +3,7 @@ This module contains the exceptions raised by the Multilspy framework.
|
||||
"""
|
||||
|
||||
|
||||
class MultilspyException(Exception):
|
||||
class LanguageServerException(Exception):
|
||||
"""
|
||||
Exceptions raised by the Multilspy framework.
|
||||
"""
|
||||
@@ -28,8 +28,8 @@ from solidlsp.lsp_protocol_handler.server import (
|
||||
make_request,
|
||||
make_response,
|
||||
)
|
||||
from solidlsp.lsp_request import SolidLspRequest
|
||||
from solidlsp.multilspy_exceptions import MultilspyException
|
||||
from solidlsp.ls_request import LanguageServerRequest
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -109,7 +109,7 @@ class SolidLanguageServerHandler:
|
||||
logger: An optional function that takes two strings (source and destination) and
|
||||
a payload dictionary, and logs the communication between the client and the server.
|
||||
"""
|
||||
self.send = SolidLspRequest(self.send_request)
|
||||
self.send = LanguageServerRequest(self.send_request)
|
||||
self.notify = LspNotification(self.send_notification)
|
||||
|
||||
self.process_launch_info = process_launch_info
|
||||
@@ -422,7 +422,7 @@ class SolidLanguageServerHandler:
|
||||
|
||||
self._log("Processing result")
|
||||
if result.is_error():
|
||||
raise MultilspyException(
|
||||
raise LanguageServerException(
|
||||
f"Could not process request {method} with params:\n{params}.\n Language server error: {result.error}"
|
||||
) from result.error
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class LogLine(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class MultilspyLogger:
|
||||
class LanguageServerLogger:
|
||||
"""
|
||||
Logger class
|
||||
"""
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
from solidlsp.lsp_protocol_handler import lsp_types
|
||||
|
||||
|
||||
class SolidLspRequest:
|
||||
class LanguageServerRequest:
|
||||
def __init__(self, send_request):
|
||||
self.send_request = send_request
|
||||
|
||||
@@ -14,9 +14,9 @@ from pathlib import Path, PurePath
|
||||
|
||||
import requests
|
||||
|
||||
from solidlsp.multilspy_exceptions import MultilspyException
|
||||
from solidlsp.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.multilspy_types import UnifiedSymbolInformation
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_types import UnifiedSymbolInformation
|
||||
|
||||
|
||||
class TextUtils:
|
||||
@@ -147,22 +147,22 @@ class FileUtils:
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def read_file(logger: MultilspyLogger, file_path: str) -> str:
|
||||
def read_file(logger: LanguageServerLogger, file_path: str) -> str:
|
||||
"""
|
||||
Reads the file at the given path and returns the contents as a string.
|
||||
"""
|
||||
if not os.path.exists(file_path):
|
||||
logger.log(f"File read '{file_path}' failed: File does not exist.", logging.ERROR)
|
||||
raise MultilspyException(f"File read '{file_path}' failed: File does not exist.")
|
||||
raise LanguageServerException(f"File read '{file_path}' failed: File does not exist.")
|
||||
try:
|
||||
with open(file_path, encoding="utf-8") as inp_file:
|
||||
return inp_file.read()
|
||||
except Exception as exc:
|
||||
logger.log(f"File read '{file_path}' failed to read with encoding 'utf-8': {exc}", logging.ERROR)
|
||||
raise MultilspyException("File read failed.") from None
|
||||
raise LanguageServerException("File read failed.") from None
|
||||
|
||||
@staticmethod
|
||||
def download_file(logger: MultilspyLogger, url: str, target_path: str) -> None:
|
||||
def download_file(logger: LanguageServerLogger, url: str, target_path: str) -> None:
|
||||
"""
|
||||
Downloads the file from the given URL to the given {target_path}
|
||||
"""
|
||||
@@ -170,15 +170,15 @@ class FileUtils:
|
||||
response = requests.get(url, stream=True, timeout=60)
|
||||
if response.status_code != 200:
|
||||
logger.log(f"Error downloading file '{url}': {response.status_code} {response.text}", logging.ERROR)
|
||||
raise MultilspyException("Error downloading file.")
|
||||
raise LanguageServerException("Error downloading file.")
|
||||
with open(target_path, "wb") as f:
|
||||
shutil.copyfileobj(response.raw, f)
|
||||
except Exception as exc:
|
||||
logger.log(f"Error downloading file '{url}': {exc}", logging.ERROR)
|
||||
raise MultilspyException("Error downloading file.") from None
|
||||
raise LanguageServerException("Error downloading file.") from None
|
||||
|
||||
@staticmethod
|
||||
def download_and_extract_archive(logger: MultilspyLogger, url: str, target_path: str, archive_type: str) -> None:
|
||||
def download_and_extract_archive(logger: LanguageServerLogger, url: str, target_path: str, archive_type: str) -> None:
|
||||
"""
|
||||
Downloads the archive from the given URL having format {archive_type} and extracts it to the given {target_path}
|
||||
"""
|
||||
@@ -203,10 +203,10 @@ class FileUtils:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
else:
|
||||
logger.log(f"Unknown archive type '{archive_type}' for extraction", logging.ERROR)
|
||||
raise MultilspyException(f"Unknown archive type '{archive_type}'")
|
||||
raise LanguageServerException(f"Unknown archive type '{archive_type}'")
|
||||
except Exception as exc:
|
||||
logger.log(f"Error extracting archive '{tmp_file_name}' obtained from '{url}': {exc}", logging.ERROR)
|
||||
raise MultilspyException("Error extracting archive.") from exc
|
||||
raise LanguageServerException("Error extracting archive.") from exc
|
||||
finally:
|
||||
for tmp_file_name in tmp_files:
|
||||
if os.path.exists(tmp_file_name):
|
||||
@@ -268,7 +268,7 @@ class PlatformUtils:
|
||||
platform_id += "-" + libc
|
||||
return PlatformId(platform_id)
|
||||
else:
|
||||
raise MultilspyException(f"Unknown platform: {system=}, {machine=}, {bitness=}")
|
||||
raise LanguageServerException(f"Unknown platform: {system=}, {machine=}, {bitness=}")
|
||||
|
||||
@staticmethod
|
||||
def _determine_windows_machine_type():
|
||||
@@ -324,7 +324,7 @@ class PlatformUtils:
|
||||
available_version_cmd_output.append(version_cmd_output)
|
||||
|
||||
if not available_version_cmd_output:
|
||||
raise MultilspyException("dotnet not found on the system")
|
||||
raise LanguageServerException("dotnet not found on the system")
|
||||
|
||||
# Check for supported versions in order of preference (latest first)
|
||||
for version_cmd_output in available_version_cmd_output:
|
||||
@@ -341,7 +341,7 @@ class PlatformUtils:
|
||||
return DotnetVersion.V4
|
||||
|
||||
# If no supported version found, raise exception with all available versions
|
||||
raise MultilspyException(
|
||||
raise LanguageServerException(
|
||||
f"No supported dotnet version found. Available versions: {', '.join(available_version_cmd_output)}. Supported versions: 4, 6, 7, 8"
|
||||
)
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
@@ -349,7 +349,7 @@ class PlatformUtils:
|
||||
result = subprocess.run(["mono", "--version"], capture_output=True, check=True)
|
||||
return DotnetVersion.VMONO
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
raise MultilspyException("dotnet or mono not found on the system")
|
||||
raise LanguageServerException("dotnet or mono not found on the system")
|
||||
|
||||
|
||||
class SymbolUtils:
|
||||
@@ -39,7 +39,7 @@ from typing import Any, Union
|
||||
|
||||
import psutil
|
||||
|
||||
from ..multilspy_exceptions import MultilspyException
|
||||
from ..ls_exceptions import LanguageServerException
|
||||
from .lsp_requests import LspNotification, LspRequest
|
||||
from .lsp_types import ErrorCodes
|
||||
|
||||
@@ -118,23 +118,6 @@ class MessageType:
|
||||
log = 4
|
||||
|
||||
|
||||
class Request:
|
||||
def __init__(self) -> None:
|
||||
self.cv = asyncio.Condition()
|
||||
self.result: PayloadLike | None = None
|
||||
self.error: Error | None = None
|
||||
|
||||
async def on_result(self, params: PayloadLike) -> None:
|
||||
self.result = params
|
||||
async with self.cv:
|
||||
self.cv.notify()
|
||||
|
||||
async def on_error(self, err: Error) -> None:
|
||||
self.error = err
|
||||
async with self.cv:
|
||||
self.cv.notify()
|
||||
|
||||
|
||||
def content_length(line: bytes) -> int | None:
|
||||
if line.startswith(b"Content-Length: "):
|
||||
_, value = line.split(b"Content-Length: ")
|
||||
@@ -145,501 +128,3 @@ def content_length(line: bytes) -> int | None:
|
||||
raise ValueError(f"Invalid Content-Length header: {value}")
|
||||
return None
|
||||
|
||||
|
||||
class LanguageServerHandler:
|
||||
"""
|
||||
This class provides the implementation of Python client for the Language Server Protocol.
|
||||
A class that launches the language server and communicates with it
|
||||
using the Language Server Protocol (LSP).
|
||||
|
||||
It provides methods for sending requests, responses, and notifications to the server
|
||||
and for registering handlers for requests and notifications from the server.
|
||||
|
||||
Uses JSON-RPC 2.0 for communication with the server over stdin/stdout.
|
||||
|
||||
Attributes:
|
||||
send: A LspRequest object that can be used to send requests to the server and
|
||||
await for the responses.
|
||||
notify: A LspNotification object that can be used to send notifications to the server.
|
||||
cmd: A string that represents the command to launch the language server process.
|
||||
process: A subprocess.Popen object that represents the language server process.
|
||||
_received_shutdown: A boolean flag that indicates whether the client has received
|
||||
a shutdown request from the server.
|
||||
request_id: An integer that represents the next available request id for the client.
|
||||
_response_handlers: A dictionary that maps request ids to Request objects that
|
||||
store the results or errors of the requests.
|
||||
on_request_handlers: A dictionary that maps method names to callback functions
|
||||
that handle requests from the server.
|
||||
on_notification_handlers: A dictionary that maps method names to callback functions
|
||||
that handle notifications from the server.
|
||||
logger: An optional function that takes two strings (source and destination) and
|
||||
a payload dictionary, and logs the communication between the client and the server.
|
||||
tasks: A dictionary that maps task ids to asyncio.Task objects that represent
|
||||
the asynchronous tasks created by the handler.
|
||||
task_counter: An integer that represents the next available task id for the handler.
|
||||
loop: An asyncio.AbstractEventLoop object that represents the event loop used by the handler.
|
||||
start_independent_lsp_process: An optional boolean flag that indicates whether to start the
|
||||
language server process in an independent process group. Default is `True`. Setting it to
|
||||
`False` means that the language server process will be in the same process group as the
|
||||
the current process, and any SIGINT and SIGTERM signals will be sent to both processes.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
process_launch_info: ProcessLaunchInfo,
|
||||
logger: Callable[[str, str, StringDict | str], None] | None = None,
|
||||
start_independent_lsp_process=True,
|
||||
) -> None:
|
||||
"""
|
||||
Params:
|
||||
cmd: A string that represents the command to launch the language server process.
|
||||
logger: An optional function that takes two strings (source and destination) and
|
||||
a payload dictionary, and logs the communication between the client and the server.
|
||||
"""
|
||||
self.send = LspRequest(self.send_request)
|
||||
self.notify = LspNotification(self.send_notification)
|
||||
|
||||
self.process_launch_info = process_launch_info
|
||||
self.process = None
|
||||
self._received_shutdown = False
|
||||
|
||||
self.request_id = 1
|
||||
self._response_handlers: dict[Any, Request] = {}
|
||||
self.on_request_handlers = {}
|
||||
self.on_notification_handlers = {}
|
||||
self.logger = logger
|
||||
self.tasks = {}
|
||||
self.task_counter = 0
|
||||
self.loop = None
|
||||
self.start_independent_lsp_process = start_independent_lsp_process
|
||||
|
||||
# Add thread locks for shared resources to prevent race conditions
|
||||
self._stdin_lock = threading.Lock()
|
||||
self._request_id_lock = threading.Lock()
|
||||
self._response_handlers_lock = threading.Lock()
|
||||
self._tasks_lock = threading.Lock()
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""
|
||||
Checks if the language server process is currently running.
|
||||
"""
|
||||
return self.process is not None and self.process.returncode is None
|
||||
|
||||
async def start(self) -> None:
|
||||
"""
|
||||
Starts the language server process and creates a task to continuously read from its stdout to handle communications
|
||||
from the server to the client
|
||||
"""
|
||||
child_proc_env = os.environ.copy()
|
||||
child_proc_env.update(self.process_launch_info.env)
|
||||
|
||||
log.info("Starting language server process via command: %s", self.process_launch_info.cmd)
|
||||
self.process = await asyncio.create_subprocess_shell(
|
||||
self.process_launch_info.cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env=child_proc_env,
|
||||
cwd=self.process_launch_info.cwd,
|
||||
start_new_session=self.start_independent_lsp_process,
|
||||
)
|
||||
|
||||
# Check if process terminated immediately
|
||||
if self.process.returncode is not None:
|
||||
log.error("Language server has already terminated/could not be started")
|
||||
# Process has already terminated
|
||||
stderr_data = await self.process.stderr.read()
|
||||
error_message = stderr_data.decode("utf-8", errors="replace")
|
||||
raise RuntimeError(f"Process terminated immediately with code {self.process.returncode}. Error: {error_message}")
|
||||
|
||||
self.loop = asyncio.get_event_loop()
|
||||
|
||||
# Use lock to prevent race conditions on tasks and task_counter during startup
|
||||
with self._tasks_lock:
|
||||
self.tasks[self.task_counter] = self.loop.create_task(self.run_forever())
|
||||
self.task_counter += 1
|
||||
self.tasks[self.task_counter] = self.loop.create_task(self.run_forever_stderr())
|
||||
self.task_counter += 1
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""
|
||||
Sends the terminate signal to the language server process and waits for it to exit, with a timeout, killing it if necessary
|
||||
"""
|
||||
# First cancel all tasks
|
||||
await self._cancel_pending_tasks()
|
||||
|
||||
process = self.process
|
||||
self.process = None
|
||||
|
||||
if not process:
|
||||
return
|
||||
|
||||
# Clean up the process
|
||||
await self._cleanup_process(process)
|
||||
|
||||
async def _cancel_pending_tasks(self):
|
||||
"""Cancel all pending tasks and wait for them to complete or timeout."""
|
||||
pending_tasks = []
|
||||
|
||||
# Use lock to safely access tasks dictionary
|
||||
with self._tasks_lock:
|
||||
for task in self.tasks.values():
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
pending_tasks.append(task)
|
||||
|
||||
if pending_tasks:
|
||||
try:
|
||||
await asyncio.wait_for(asyncio.gather(*pending_tasks, return_exceptions=True), timeout=5.0)
|
||||
except (TimeoutError, Exception):
|
||||
pass
|
||||
|
||||
# Clear tasks dictionary under lock
|
||||
with self._tasks_lock:
|
||||
self.tasks = {}
|
||||
|
||||
async def _cleanup_process(self, process):
|
||||
"""Clean up a process: close stdin, terminate/kill process, close stdout/stderr."""
|
||||
# Close stdin first to prevent deadlocks
|
||||
# See: https://bugs.python.org/issue35539
|
||||
self._safely_close_pipe(process.stdin)
|
||||
|
||||
# Terminate/kill the process if it's still running
|
||||
if process.returncode is None:
|
||||
await self._terminate_or_kill_process(process)
|
||||
|
||||
# Close stdout and stderr pipes after process has exited
|
||||
# This is essential to prevent "I/O operation on closed pipe" errors and
|
||||
# "Event loop is closed" errors during garbage collection
|
||||
# See: https://bugs.python.org/issue41320 and https://github.com/python/cpython/issues/88050
|
||||
self._safely_close_pipe(process.stdout)
|
||||
self._safely_close_pipe(process.stderr)
|
||||
|
||||
# Small delay to ensure OS has released file handles
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
def _safely_close_pipe(self, pipe):
|
||||
"""Safely close a pipe, ignoring any exceptions."""
|
||||
if pipe:
|
||||
try:
|
||||
pipe.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _terminate_or_kill_process(self, process):
|
||||
"""Try to terminate the process gracefully, then forcefully if necessary."""
|
||||
# First try to terminate the process tree gracefully
|
||||
self._signal_process_tree(process, terminate=True)
|
||||
|
||||
# Wait for the process to exit (with timeout)
|
||||
try:
|
||||
await asyncio.wait_for(process.wait(), timeout=10)
|
||||
except (TimeoutError, Exception):
|
||||
# If termination failed, forcefully kill the process tree
|
||||
self._signal_process_tree(process, terminate=False)
|
||||
try:
|
||||
# Give it one more chance to exit
|
||||
await asyncio.wait_for(process.wait(), timeout=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _signal_process_tree(self, process, terminate=True):
|
||||
"""Send signal (terminate or kill) to the process and all its children."""
|
||||
signal_method = "terminate" if terminate else "kill"
|
||||
|
||||
# Try to get the parent process
|
||||
parent = None
|
||||
try:
|
||||
parent = psutil.Process(process.pid)
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception):
|
||||
pass
|
||||
|
||||
# If we have the parent process and it's running, signal the entire tree
|
||||
if parent and parent.is_running():
|
||||
# Signal children first
|
||||
for child in parent.children(recursive=True):
|
||||
try:
|
||||
getattr(child, signal_method)()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception):
|
||||
pass
|
||||
|
||||
# Then signal the parent
|
||||
try:
|
||||
getattr(parent, signal_method)()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception):
|
||||
pass
|
||||
else:
|
||||
# Fall back to direct process signaling
|
||||
try:
|
||||
getattr(process, signal_method)()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""
|
||||
Perform the shutdown sequence for the client, including sending the shutdown request to the server and notifying it of exit
|
||||
"""
|
||||
self._log("Sending shutdown request to server")
|
||||
await self.send.shutdown()
|
||||
self._log("Received shutdown response from server")
|
||||
self._received_shutdown = True
|
||||
self._log("Sending exit notification to server")
|
||||
self.notify.exit()
|
||||
self._log("Sent exit notification to server")
|
||||
if self.process and self.process.stdout:
|
||||
self.process.stdout.set_exception(StopLoopException())
|
||||
# This yields the control to the event loop to allow the exception to be handled
|
||||
# in the run_forever and run_forever_stderr methods
|
||||
await asyncio.sleep(0)
|
||||
|
||||
def _log(self, message: str | StringDict) -> None:
|
||||
"""
|
||||
Create a log message
|
||||
"""
|
||||
if self.logger is not None:
|
||||
self.logger("client", "logger", message)
|
||||
|
||||
async def run_forever(self) -> bool:
|
||||
"""
|
||||
Continuously read from the language server process stdout and handle the messages
|
||||
invoking the registered response and notification handlers
|
||||
"""
|
||||
try:
|
||||
while self.process and self.process.stdout and not self.process.stdout.at_eof():
|
||||
line = await self.process.stdout.readline()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
num_bytes = content_length(line)
|
||||
except ValueError:
|
||||
continue
|
||||
if num_bytes is None:
|
||||
continue
|
||||
while line and line.strip():
|
||||
line = await self.process.stdout.readline()
|
||||
if not line:
|
||||
continue
|
||||
body = await self.process.stdout.readexactly(num_bytes)
|
||||
|
||||
# Use lock to prevent race conditions on tasks and task_counter
|
||||
with self._tasks_lock:
|
||||
self.tasks[self.task_counter] = asyncio.get_event_loop().create_task(self._handle_body(body))
|
||||
self.task_counter += 1
|
||||
except (BrokenPipeError, ConnectionResetError, StopLoopException):
|
||||
pass
|
||||
return self._received_shutdown
|
||||
|
||||
async def run_forever_stderr(self) -> None:
|
||||
"""
|
||||
Continuously read from the language server process stderr and log the messages
|
||||
"""
|
||||
try:
|
||||
while self.process and self.process.stderr and not self.process.stderr.at_eof():
|
||||
line = await self.process.stderr.readline()
|
||||
if not line:
|
||||
continue
|
||||
self._log("LSP stderr: " + line.decode(ENCODING, errors="replace"))
|
||||
except (BrokenPipeError, ConnectionResetError, StopLoopException):
|
||||
pass
|
||||
|
||||
async def _handle_body(self, body: bytes) -> None:
|
||||
"""
|
||||
Parse the body text received from the language server process and invoke the appropriate handler
|
||||
"""
|
||||
try:
|
||||
await self._receive_payload(json.loads(body))
|
||||
except OSError as ex:
|
||||
self._log(f"malformed {ENCODING}: {ex}")
|
||||
except UnicodeDecodeError as ex:
|
||||
self._log(f"malformed {ENCODING}: {ex}")
|
||||
except json.JSONDecodeError as ex:
|
||||
self._log(f"malformed JSON: {ex}")
|
||||
|
||||
async def _receive_payload(self, payload: StringDict) -> None:
|
||||
"""
|
||||
Determine if the payload received from server is for a request, response, or notification and invoke the appropriate handler
|
||||
"""
|
||||
if self.logger:
|
||||
self.logger("server", "client", payload)
|
||||
try:
|
||||
if "method" in payload:
|
||||
if "id" in payload:
|
||||
await self._request_handler(payload)
|
||||
else:
|
||||
await self._notification_handler(payload)
|
||||
elif "id" in payload:
|
||||
await self._response_handler(payload)
|
||||
else:
|
||||
self._log(f"Unknown payload type: {payload}")
|
||||
except Exception as err:
|
||||
self._log(f"Error handling server payload: {err}")
|
||||
|
||||
def send_notification(self, method: str, params: dict | None = None) -> None:
|
||||
"""
|
||||
Send notification pertaining to the given method to the server with the given parameters
|
||||
"""
|
||||
self._send_payload_sync(make_notification(method, params))
|
||||
|
||||
def send_response(self, request_id: Any, params: PayloadLike) -> None:
|
||||
"""
|
||||
Send response to the given request id to the server with the given parameters
|
||||
"""
|
||||
# Use lock to prevent race conditions on tasks and task_counter
|
||||
with self._tasks_lock:
|
||||
self.tasks[self.task_counter] = asyncio.get_event_loop().create_task(self._send_payload(make_response(request_id, params)))
|
||||
self.task_counter += 1
|
||||
|
||||
def send_error_response(self, request_id: Any, err: Error) -> None:
|
||||
"""
|
||||
Send error response to the given request id to the server with the given error
|
||||
"""
|
||||
# Use lock to prevent race conditions on tasks and task_counter
|
||||
with self._tasks_lock:
|
||||
self.tasks[self.task_counter] = asyncio.get_event_loop().create_task(self._send_payload(make_error_response(request_id, err)))
|
||||
self.task_counter += 1
|
||||
|
||||
async def send_request(self, method: str, params: dict | None = None) -> PayloadLike:
|
||||
"""
|
||||
Send request to the server, register the request id, and wait for the response
|
||||
"""
|
||||
request = Request()
|
||||
|
||||
# Use lock to prevent race conditions on request_id and _response_handlers
|
||||
with self._request_id_lock:
|
||||
request_id = self.request_id
|
||||
self.request_id += 1
|
||||
|
||||
with self._response_handlers_lock:
|
||||
self._response_handlers[request_id] = request
|
||||
|
||||
async with request.cv:
|
||||
await self._send_payload(make_request(method, request_id, params))
|
||||
self._log(f"Waiting for asyncio condition for request {method} with params:\n{params}")
|
||||
await request.cv.wait()
|
||||
self._log("Finished waiting, processing result")
|
||||
if isinstance(request.error, Error):
|
||||
raise MultilspyException(
|
||||
f"Could not process request {method} with params:\n{params}.\n Language server error: {request.error}"
|
||||
) from request.error
|
||||
self._log(f"Returning non-error result, which is:\n{request.result}")
|
||||
return request.result
|
||||
|
||||
def _send_payload_sync(self, payload: StringDict) -> None:
|
||||
"""
|
||||
Send the payload to the server by writing to its stdin synchronously
|
||||
"""
|
||||
if not self.process or not self.process.stdin:
|
||||
return
|
||||
msg = create_message(payload)
|
||||
if self.logger:
|
||||
self.logger("client", "server", payload)
|
||||
|
||||
# Use lock to prevent concurrent writes to stdin that cause buffer corruption
|
||||
with self._stdin_lock:
|
||||
try:
|
||||
self.process.stdin.writelines(msg)
|
||||
except (BrokenPipeError, ConnectionResetError, OSError) as e:
|
||||
# Log the error but don't raise to prevent cascading failures
|
||||
if self.logger:
|
||||
self.logger("client", "logger", f"Failed to write to stdin: {e}")
|
||||
return
|
||||
|
||||
async def _send_payload(self, payload: StringDict) -> None:
|
||||
"""
|
||||
Send the payload to the server by writing to its stdin asynchronously.
|
||||
"""
|
||||
if not self.process or not self.process.stdin:
|
||||
return
|
||||
self._log(payload)
|
||||
msg = create_message(payload)
|
||||
|
||||
# Use lock to prevent concurrent writes to stdin that cause buffer corruption
|
||||
with self._stdin_lock:
|
||||
try:
|
||||
self.process.stdin.writelines(msg)
|
||||
await self.process.stdin.drain()
|
||||
except (BrokenPipeError, ConnectionResetError, OSError) as e:
|
||||
# Log the error but don't raise to prevent cascading failures
|
||||
if self.logger:
|
||||
self.logger("client", "logger", f"Failed to write to stdin: {e}")
|
||||
return
|
||||
|
||||
def on_request(self, method: str, cb) -> None:
|
||||
"""
|
||||
Register the callback function to handle requests from the server to the client for the given method
|
||||
"""
|
||||
self.on_request_handlers[method] = cb
|
||||
|
||||
def on_notification(self, method: str, cb) -> None:
|
||||
"""
|
||||
Register the callback function to handle notifications from the server to the client for the given method
|
||||
"""
|
||||
self.on_notification_handlers[method] = cb
|
||||
|
||||
async def _response_handler(self, response: StringDict) -> None:
|
||||
"""
|
||||
Handle the response received from the server for a request, using the id to determine the request
|
||||
"""
|
||||
with self._response_handlers_lock:
|
||||
request = self._response_handlers.pop(response["id"])
|
||||
|
||||
if "result" in response and "error" not in response:
|
||||
await request.on_result(response["result"])
|
||||
elif "result" not in response and "error" in response:
|
||||
await request.on_error(Error.from_lsp(response["error"]))
|
||||
else:
|
||||
await request.on_error(Error(ErrorCodes.InvalidRequest, ""))
|
||||
|
||||
async def _request_handler(self, response: StringDict) -> None:
|
||||
"""
|
||||
Handle the request received from the server: call the appropriate callback function and return the result
|
||||
"""
|
||||
method = response.get("method", "")
|
||||
params = response.get("params")
|
||||
request_id = response.get("id")
|
||||
handler = self.on_request_handlers.get(method)
|
||||
if not handler:
|
||||
self.send_error_response(
|
||||
request_id,
|
||||
Error(
|
||||
ErrorCodes.MethodNotFound,
|
||||
f"method '{method}' not handled on client.",
|
||||
),
|
||||
)
|
||||
return
|
||||
try:
|
||||
self.send_response(request_id, await handler(params))
|
||||
except Error as ex:
|
||||
self.send_error_response(request_id, ex)
|
||||
except Exception as ex:
|
||||
self.send_error_response(request_id, Error(ErrorCodes.InternalError, str(ex)))
|
||||
|
||||
async def _notification_handler(self, response: StringDict) -> None:
|
||||
"""
|
||||
Handle the notification received from the server: call the appropriate callback function
|
||||
"""
|
||||
method = response.get("method", "")
|
||||
params = response.get("params")
|
||||
handler = self.on_notification_handlers.get(method)
|
||||
if not handler:
|
||||
self._log(f"unhandled {method}")
|
||||
return
|
||||
try:
|
||||
await handler(params)
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
except Exception as ex:
|
||||
if (not self._received_shutdown) and self.logger:
|
||||
self.logger(
|
||||
"client",
|
||||
"logger",
|
||||
str(
|
||||
{
|
||||
"type": MessageType.error,
|
||||
"message": str(ex),
|
||||
"method": method,
|
||||
"params": params,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import os
|
||||
import pathlib
|
||||
|
||||
|
||||
class MultilspySettings:
|
||||
class SolidLSPSettings:
|
||||
"""
|
||||
Provides the various settings for multilspy.
|
||||
"""
|
||||
@@ -1,28 +0,0 @@
|
||||
"""
|
||||
This module provides type-helpers used across multilspy implementation
|
||||
"""
|
||||
|
||||
import inspect
|
||||
from collections.abc import Callable
|
||||
from typing import TypeVar
|
||||
|
||||
R = TypeVar("R", bound=object)
|
||||
|
||||
|
||||
def ensure_all_methods_implemented(
|
||||
source_cls: type[object],
|
||||
) -> Callable[[type[R]], type[R]]:
|
||||
"""
|
||||
A decorator to ensure that all methods of source_cls class are implemented in the decorated class.
|
||||
"""
|
||||
|
||||
def check_all_methods_implemented(target_cls: R) -> R:
|
||||
for name, _ in inspect.getmembers(source_cls, inspect.isfunction):
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
if name not in target_cls.__dict__ or not callable(target_cls.__dict__[name]):
|
||||
raise NotImplementedError(f"{name} is not implemented in {target_cls}")
|
||||
|
||||
return target_cls
|
||||
|
||||
return check_all_methods_implemented
|
||||
+4
-4
@@ -6,8 +6,8 @@ from sensai.util.logging import configure
|
||||
|
||||
from serena.util.file_system import GitignoreParser
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language, MultilspyConfig
|
||||
from solidlsp.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
|
||||
configure(level=logging.DEBUG)
|
||||
|
||||
@@ -40,8 +40,8 @@ def create_ls(
|
||||
gitignore_parser = GitignoreParser(str(repo_path))
|
||||
for spec in gitignore_parser.get_ignore_specs():
|
||||
ignored_paths.extend(spec.patterns)
|
||||
config = MultilspyConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication)
|
||||
logger = MultilspyLogger(log_level=log_level)
|
||||
config = LanguageServerConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication)
|
||||
logger = LanguageServerLogger(log_level=log_level)
|
||||
return SolidLanguageServer.create(config, logger, repo_path)
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.multilspy_utils import SymbolUtils
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
|
||||
|
||||
@pytest.mark.go
|
||||
|
||||
@@ -3,8 +3,8 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.multilspy_utils import SymbolUtils
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
|
||||
|
||||
@pytest.mark.java
|
||||
|
||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
|
||||
@pytest.mark.php
|
||||
|
||||
@@ -11,7 +11,7 @@ import pytest
|
||||
|
||||
from serena.text_utils import LineType
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
|
||||
@pytest.mark.python
|
||||
|
||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import create_ls
|
||||
|
||||
# This mark will be applied to all tests in this module
|
||||
|
||||
@@ -11,8 +11,8 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.multilspy_types import SymbolKind
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
|
||||
pytestmark = pytest.mark.python
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.multilspy_utils import SymbolUtils
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
|
||||
|
||||
@pytest.mark.rust
|
||||
|
||||
@@ -3,8 +3,8 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.multilspy_utils import SymbolUtils
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
|
||||
|
||||
@pytest.mark.typescript
|
||||
|
||||
@@ -7,7 +7,7 @@ import pytest
|
||||
|
||||
from serena.agent import FindReferencingSymbolsTool, FindSymbolTool, Project, ProjectConfig, SerenaAgent, SerenaConfigBase
|
||||
from serena.process_isolated_agent import ProcessIsolatedSerenaAgent
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import get_repo_path
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import Literal
|
||||
import pytest
|
||||
|
||||
from serena.symbol import CodeDiff
|
||||
from solidlsp.multilspy_config import Language
|
||||
from solidlsp.ls_config import Language
|
||||
from src.serena.symbol import SymbolManager
|
||||
from test.conftest import create_ls, get_repo_path
|
||||
|
||||
|
||||
Reference in New Issue
Block a user