Merge branch 'fix-ls-interaction'

This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2025-07-27 23:10:56 +02:00
commit 66b17cd9e2
9 files changed
+233 -174

No files matched your search

+14 -8
View File
@@ -1,6 +1,5 @@
import inspect
import os
import traceback
from abc import ABC
from collections.abc import Iterable
from dataclasses import dataclass
@@ -16,6 +15,7 @@ from serena.prompt_factory import PromptFactory
from serena.symbol import LanguageServerSymbolRetriever
from serena.util.class_decorators import singleton
from serena.util.inspection import iter_subclasses
from solidlsp.ls_exceptions import SolidLSPException
if TYPE_CHECKING:
from serena.agent import LinesRead, MemoriesManager, SerenaAgent
@@ -237,18 +237,24 @@ class Tool(Component):
self.agent.reset_language_server()
# apply the actual tool
result = apply_fn(**kwargs)
try:
result = apply_fn(**kwargs)
except SolidLSPException as e:
if e.is_language_server_terminated():
log.error(f"Language server terminated while executing tool ({e}). Restarting the language server and retrying ...")
self.agent.reset_language_server()
result = apply_fn(**kwargs)
else:
raise
# record tool usage
self.agent.record_tool_usage_if_enabled(kwargs, result, self)
except Exception as e:
if not catch_exceptions:
raise
msg = f"Error executing tool: {e}\n{traceback.format_exc()}"
log.error(
f"Error executing tool: {e}. "
f"Consider restarting the language server to solve this (especially, if it's a timeout of a symbolic operation)",
exc_info=e,
)
msg = f"Error executing tool: {e}"
log.error(f"Error executing tool: {e}", exc_info=e)
result = msg
if log_call:
@@ -20,7 +20,7 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import LanguageServerException
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PathUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
@@ -246,7 +246,7 @@ class CSharpLanguageServer(SolidLanguageServer):
elif system == "linux":
return "linux-x64" if machine in ["x86_64", "amd64"] else "linux-arm64"
else:
raise LanguageServerException(f"Unsupported platform: {system} {machine}")
raise SolidLSPException(f"Unsupported platform: {system} {machine}")
@staticmethod
def _get_runtime_dependencies(runtime_id: str) -> tuple[RuntimeDependency, RuntimeDependency]:
@@ -261,9 +261,9 @@ class CSharpLanguageServer(SolidLanguageServer):
dotnet_runtime_dep = dep
if not lang_server_dep:
raise LanguageServerException(f"No C# language server dependency found for platform {runtime_id}")
raise SolidLSPException(f"No C# language server dependency found for platform {runtime_id}")
if not dotnet_runtime_dep:
raise LanguageServerException(f"No .NET runtime dependency found for platform {runtime_id}")
raise SolidLSPException(f"No .NET runtime dependency found for platform {runtime_id}")
return lang_server_dep, dotnet_runtime_dep
@@ -310,7 +310,7 @@ class CSharpLanguageServer(SolidLanguageServer):
cls._extract_language_server(lang_server_dep, package_path, server_dir)
if not server_dll.exists():
raise LanguageServerException("Microsoft.CodeAnalysis.LanguageServer.dll not found after extraction")
raise SolidLSPException("Microsoft.CodeAnalysis.LanguageServer.dll not found after extraction")
# Make executable on Unix systems
if platform.system().lower() != "windows":
@@ -336,7 +336,7 @@ class CSharpLanguageServer(SolidLanguageServer):
source_dir = possible_dir
break
else:
raise LanguageServerException(f"Could not find language server files in package. Searched in {package_path}")
raise SolidLSPException(f"Could not find language server files in package. Searched in {package_path}")
# Copy files to cache directory
server_dir.mkdir(parents=True, exist_ok=True)
@@ -370,7 +370,7 @@ class CSharpLanguageServer(SolidLanguageServer):
break
if not package_base_address:
raise LanguageServerException("Could not find package base address in Azure NuGet feed")
raise SolidLSPException("Could not find package base address in Azure NuGet feed")
# Construct the download URL for the specific package
package_id_lower = package_name.lower()
@@ -397,7 +397,7 @@ class CSharpLanguageServer(SolidLanguageServer):
return package_extract_dir
except Exception as e:
raise LanguageServerException(
raise SolidLSPException(
f"Failed to download package {package_name} version {package_version} from Azure NuGet feed: {e}"
) from e
@@ -462,7 +462,7 @@ class CSharpLanguageServer(SolidLanguageServer):
return str(dotnet_exe)
except Exception as e:
raise LanguageServerException(f"Failed to download .NET 9 runtime from {url}: {e}") from e
raise SolidLSPException(f"Failed to download .NET 9 runtime from {url}: {e}") from e
def _get_initialize_params(self) -> InitializeParams:
"""
@@ -637,7 +637,7 @@ class CSharpLanguageServer(SolidLanguageServer):
self.server.start()
except Exception as e:
self.logger.log(f"Failed to start language server process: {e}", logging.ERROR)
raise LanguageServerException(f"Failed to start C# language server: {e}")
raise SolidLSPException(f"Failed to start C# language server: {e}")
# Send initialization
initialize_params = self._get_initialize_params()
@@ -647,7 +647,7 @@ class CSharpLanguageServer(SolidLanguageServer):
init_response = self.server.send.initialize(initialize_params)
self.logger.log(f"Received initialize response: {init_response}", logging.DEBUG)
except Exception as e:
raise LanguageServerException(f"Failed to initialize C# language server for {self.repository_root_path}: {e}") from e
raise SolidLSPException(f"Failed to initialize C# language server for {self.repository_root_path}: {e}") from e
# Apply diagnostic capabilities
self._force_pull_diagnostics(init_response)
+2 -2
View File
@@ -14,7 +14,7 @@ from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import LanguageServerException
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
@@ -71,7 +71,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 LanguageServerException("No SLN file found in repository")
raise SolidLSPException("No SLN file found in repository")
cmd = " ".join(
[
+12 -12
View File
@@ -22,7 +22,7 @@ from serena.text_utils import MatchedConsecutiveLines
from serena.util.file_system import match_path
from solidlsp import ls_types
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_exceptions import LanguageServerException
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_handler import SolidLanguageServerHandler
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import FileUtils, PathUtils, TextUtils
@@ -31,7 +31,7 @@ from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes
from solidlsp.lsp_protocol_handler.lsp_constants import LSPConstants
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink, SymbolKind
from solidlsp.lsp_protocol_handler.server import (
Error,
LSPError,
ProcessLaunchInfo,
StringDict,
)
@@ -228,7 +228,7 @@ class SolidLanguageServer(ABC):
else:
logger.log(f"Language {config.code_language} is not supported", logging.ERROR)
raise LanguageServerException(f"Language {config.code_language} is not supported")
raise SolidLSPException(f"Language {config.code_language} is not supported")
ls.set_request_timeout(timeout)
return ls
@@ -448,7 +448,7 @@ class SolidLanguageServer(ABC):
"open_file called before Language Server started",
logging.ERROR,
)
raise LanguageServerException("Language Server not started")
raise SolidLSPException("Language Server not started")
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
uri = pathlib.Path(absolute_file_path).as_uri()
@@ -504,7 +504,7 @@ class SolidLanguageServer(ABC):
"insert_text_at_position called before Language Server started",
logging.ERROR,
)
raise LanguageServerException("Language Server not started")
raise SolidLSPException("Language Server not started")
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
uri = pathlib.Path(absolute_file_path).as_uri()
@@ -550,7 +550,7 @@ class SolidLanguageServer(ABC):
"insert_text_at_position called before Language Server started",
logging.ERROR,
)
raise LanguageServerException("Language Server not started")
raise SolidLSPException("Language Server not started")
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
uri = pathlib.Path(absolute_file_path).as_uri()
@@ -594,7 +594,7 @@ class SolidLanguageServer(ABC):
"request_definition called before Language Server started",
logging.ERROR,
)
raise LanguageServerException("Language Server not started")
raise SolidLSPException("Language Server not started")
with self.open_file(relative_file_path):
# sending request to the language server and waiting for response
@@ -686,14 +686,14 @@ class SolidLanguageServer(ABC):
"request_references called before Language Server started",
logging.ERROR,
)
raise LanguageServerException("Language Server not started")
raise SolidLSPException("Language Server not started")
with self.open_file(relative_file_path):
try:
response = self._send_references_request(relative_file_path, line=line, column=column)
except Exception as e:
# Catch LSP internal error (-32603) and raise a more informative exception
if isinstance(e, Error) and getattr(e, "code", None) == -32603:
if isinstance(e, LSPError) 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. "
@@ -745,7 +745,7 @@ class SolidLanguageServer(ABC):
"request_text_document_diagnostics called before Language Server started",
logging.ERROR,
)
raise LanguageServerException("Language Server not started")
raise SolidLSPException("Language Server not started")
with self.open_file(relative_file_path):
response = self.server.send.text_document_diagnostic(
@@ -1307,7 +1307,7 @@ class SolidLanguageServer(ABC):
"request_referencing_symbols called before Language Server started",
logging.ERROR,
)
raise LanguageServerException("Language Server not started")
raise SolidLSPException("Language Server not started")
# First, get all references to the symbol
references = self.request_references(relative_file_path, line, column)
@@ -1579,7 +1579,7 @@ class SolidLanguageServer(ABC):
"request_defining_symbol called before Language Server started",
logging.ERROR,
)
raise LanguageServerException("Language Server not started")
raise SolidLSPException("Language Server not started")
# Get the definition location(s)
definitions = self.request_definition(relative_file_path, line, column)
+32 -7
View File
@@ -1,15 +1,40 @@
"""
This module contains the exceptions raised by the Multilspy framework.
This module contains the exceptions raised by the framework.
"""
class LanguageServerException(Exception):
"""
Exceptions raised by the Multilspy framework.
"""
def __init__(self, message: str):
class SolidLSPException(Exception):
def __init__(self, message: str, cause: Exception | None = None):
"""
Initializes the exception with the given message.
:param message: the message describing the exception
:param cause: the original exception that caused this exception, if any.
For exceptions raised during request handling, this is typically
* an LSPError for errors returned by the LSP server
* LanguageServerTerminatedException for errors due to the language server having terminated.
"""
self.cause = cause
super().__init__(message)
def is_language_server_terminated(self):
"""
:return: True if the exception is caused by the language server having terminated as indicated
by the causing exception being an instance of LanguageServerTerminatedException.
"""
from .ls_handler import LanguageServerTerminatedException
return isinstance(self.cause, LanguageServerTerminatedException)
def __str__(self) -> str:
"""
Returns a string representation of the exception.
"""
s = super().__str__()
if self.cause:
if "\n" in s:
s += "\n"
else:
s += " "
s += f"(caused by {self.cause})"
return s
+87 -65
View File
@@ -12,14 +12,15 @@ from queue import Queue
from typing import Any
import psutil
from sensai.util.string import ToStringMixin
from solidlsp.ls_exceptions import LanguageServerException
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_request import LanguageServerRequest
from solidlsp.lsp_protocol_handler.lsp_requests import LspNotification
from solidlsp.lsp_protocol_handler.lsp_types import ErrorCodes
from solidlsp.lsp_protocol_handler.server import (
ENCODING,
Error,
LSPError,
MessageType,
PayloadLike,
ProcessLaunchInfo,
@@ -35,23 +36,50 @@ from solidlsp.lsp_protocol_handler.server import (
log = logging.getLogger(__name__)
class Request:
class LanguageServerTerminatedException(Exception):
"""
Exception raised when the language server process has terminated unexpectedly.
"""
def __init__(self, message: str, cause: Exception | None = None) -> None:
super().__init__(message)
self.message = message
self.cause = cause
def __str__(self) -> str:
return f"LanguageServerTerminatedException: {self.message}" + (f"; Cause: {self.cause}" if self.cause else "")
class Request(ToStringMixin):
@dataclass
class Result:
payload: PayloadLike | None = None
error: Error | None = None
error: Exception | None = None
def is_error(self) -> bool:
return self.error is not None
def __init__(self) -> None:
def __init__(self, request_id: int, method: str) -> None:
self._request_id = request_id
self._method = method
self._status = "pending"
self._result_queue = Queue()
def _tostring_includes(self) -> list[str]:
return ["_request_id", "_status", "_method"]
def on_result(self, params: PayloadLike) -> None:
self._status = "completed"
self._result_queue.put(Request.Result(payload=params))
def on_error(self, err: Error) -> None:
def on_error(self, err: Exception) -> None:
"""
:param err: the error that occurred while processing the request (typically an LSPError
for errors returned by the LS or LanguageServerTerminatedException if the error
is due to the language server process terminating unexpectedly).
"""
self._status = "error"
self._result_queue.put(Request.Result(error=err))
def get_result(self, timeout: float | None = None) -> Result:
@@ -75,10 +103,8 @@ class SolidLanguageServerHandler:
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
_pending_requests: 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.
@@ -104,15 +130,15 @@ class SolidLanguageServerHandler:
start_independent_lsp_process=True,
request_timeout: float | None = None,
) -> None:
self.send = LanguageServerRequest(self.send_request)
self.send = LanguageServerRequest(self)
self.notify = LspNotification(self.send_notification)
self.process_launch_info = process_launch_info
self.process = None
self._received_shutdown = False
self._is_shutting_down = False
self.request_id = 1
self._response_handlers: dict[Any, Request] = {}
self._pending_requests: dict[Any, Request] = {}
self.on_request_handlers = {}
self.on_notification_handlers = {}
self.logger = logger
@@ -176,12 +202,12 @@ class SolidLanguageServerHandler:
# start threads to read stdout and stderr of the process
threading.Thread(
target=self.run_forever,
target=self._read_ls_process_stdout,
name="LSP-stdout-reader",
daemon=True,
).start()
threading.Thread(
target=self.run_forever_stderr,
target=self._read_ls_process_stderr,
name="LSP-stderr-reader",
daemon=True,
).start()
@@ -225,21 +251,6 @@ class SolidLanguageServerHandler:
# First try to terminate the process tree gracefully
self._signal_process_tree(process, terminate=True)
# TODO
"""
# Wait for the process to exit (with timeout)
try:
asyncio.wait_for(process.wait(), timeout=10)
except (asyncio.TimeoutError, Exception):
# If termination failed, forcefully kill the process tree
self._signal_process_tree(process, terminate=False)
try:
# Give it one more chance to exit
await asyncio.wait_for(process.wait(), timeout=2)
except Exception:
pass
"""
def _signal_process_tree(self, process, terminate=True):
"""Send signal (terminate or kill) to the process and all its children."""
signal_method = "terminate" if terminate else "kill"
@@ -276,21 +287,13 @@ class SolidLanguageServerHandler:
"""
Perform the shutdown sequence for the client, including sending the shutdown request to the server and notifying it of exit
"""
self._is_shutting_down = True
self._log("Sending shutdown request to server")
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")
# TODO
"""
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:
"""
@@ -302,27 +305,26 @@ class SolidLanguageServerHandler:
@staticmethod
def _read_bytes_from_process(process, stream, num_bytes):
"""Read exactly num_bytes from process stdout"""
if process.poll() is not None:
# Process has terminated, check if we can still read
pass
data = b""
while len(data) < num_bytes:
chunk = stream.read(num_bytes - len(data))
if not chunk:
if process.poll() is not None:
raise EOFError(f"Process terminated. Expected {num_bytes} bytes, got {len(data)}")
# Process still running but no data available yet
time.sleep(0.01) # Small delay
raise LanguageServerTerminatedException(
f"Process terminated while trying to read response (read {num_bytes} of {len(data)} bytes before termination)"
)
# Process still running but no data available yet, retry after a short delay
time.sleep(0.01)
continue
data += chunk
return data
def run_forever(self) -> bool:
def _read_ls_process_stdout(self) -> None:
"""
Continuously read from the language server process stdout and handle the messages
invoking the registered response and notification handlers
"""
exception: Exception | None = None
try:
while self.process and self.process.stdout and self.process.stdout.readable():
line = self.process.stdout.readline()
@@ -341,11 +343,20 @@ class SolidLanguageServerHandler:
body = self._read_bytes_from_process(self.process, self.process.stdout, num_bytes)
self._handle_body(body)
except (BrokenPipeError, ConnectionResetError):
pass
return self._received_shutdown
except LanguageServerTerminatedException as e:
exception = e
except (BrokenPipeError, ConnectionResetError) as e:
exception = LanguageServerTerminatedException("Language server process terminated while reading stdout", cause=e)
except Exception as e:
exception = LanguageServerTerminatedException("Unexpected error while reading stdout from language server process", cause=e)
log.info("Language server stdout reader thread has terminated")
if not self._is_shutting_down:
if exception is None:
exception = LanguageServerTerminatedException("Language server stdout read process terminated unexpectedly")
log.error(str(exception))
self._cancel_pending_requests(exception)
def run_forever_stderr(self) -> None:
def _read_ls_process_stderr(self) -> None:
"""
Continuously read from the language server process stderr and log the messages
"""
@@ -357,6 +368,7 @@ class SolidLanguageServerHandler:
self._log("LSP stderr: " + line.decode(ENCODING, errors="replace"))
except (BrokenPipeError, ConnectionResetError):
pass
log.info("Language server stderr reader thread has terminated")
def _handle_body(self, body: bytes) -> None:
"""
@@ -402,37 +414,47 @@ class SolidLanguageServerHandler:
"""
self._send_payload(make_response(request_id, params))
def send_error_response(self, request_id: Any, err: Error) -> None:
def send_error_response(self, request_id: Any, err: LSPError) -> 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
self._send_payload(make_error_response(request_id, err))
def _cancel_pending_requests(self, exception: Exception) -> None:
"""
Cancel all pending requests by setting their results to an error
"""
with self._response_handlers_lock:
log.info("Cancelling %d pending language server requests", len(self._pending_requests))
for request in self._pending_requests.values():
log.info("Cancelling %s", request)
request.on_error(exception)
self._pending_requests.clear()
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
request = Request(request_id=request_id, method=method)
log.debug("Starting: %s", request)
with self._response_handlers_lock:
self._response_handlers[request_id] = request
self._pending_requests[request_id] = request
self._send_payload(make_request(method, request_id, params))
self._log(f"Waiting for response to request {method} with params:\n{params}")
result = request.get_result(timeout=self._request_timeout)
log.debug("Completed: %s", request)
self._log("Processing result")
if result.is_error():
raise LanguageServerException(
f"Could not process request {method} with params:\n{params}.\n Language server error: {result.error}"
) from result.error
raise SolidLSPException(f"Error processing request {method} with params:\n{params}", cause=result.error) from result.error
self._log(f"Returning non-error result, which is:\n{result.payload}")
return result.payload
@@ -474,14 +496,14 @@ class SolidLanguageServerHandler:
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"])
request = self._pending_requests.pop(response["id"])
if "result" in response and "error" not in response:
request.on_result(response["result"])
elif "result" not in response and "error" in response:
request.on_error(Error.from_lsp(response["error"]))
request.on_error(LSPError.from_lsp(response["error"]))
else:
request.on_error(Error(ErrorCodes.InvalidRequest, ""))
request.on_error(LSPError(ErrorCodes.InvalidRequest, ""))
def _request_handler(self, response: StringDict) -> None:
"""
@@ -494,7 +516,7 @@ class SolidLanguageServerHandler:
if not handler:
self.send_error_response(
request_id,
Error(
LSPError(
ErrorCodes.MethodNotFound,
f"method '{method}' not handled on client.",
),
@@ -502,10 +524,10 @@ class SolidLanguageServerHandler:
return
try:
self.send_response(request_id, handler(params))
except Error as ex:
except LSPError as ex:
self.send_error_response(request_id, ex)
except Exception as ex:
self.send_error_response(request_id, Error(ErrorCodes.InternalError, str(ex)))
self.send_error_response(request_id, LSPError(ErrorCodes.InternalError, str(ex)))
def _notification_handler(self, response: StringDict) -> None:
"""
@@ -522,7 +544,7 @@ class SolidLanguageServerHandler:
except asyncio.CancelledError:
return
except Exception as ex:
if (not self._received_shutdown) and self.logger:
if (not self._is_shutting_down) and self.logger:
self.logger(
"client",
"logger",
+60 -54
View File
@@ -1,11 +1,17 @@
from typing import Union
from typing import TYPE_CHECKING, Any, Union
from solidlsp.lsp_protocol_handler import lsp_types
if TYPE_CHECKING:
from .ls_handler import SolidLanguageServerHandler
class LanguageServerRequest:
def __init__(self, send_request):
self.send_request = send_request
def __init__(self, handler: "SolidLanguageServerHandler"):
self.handler = handler
def _send_request(self, method: str, params: Any | None = None) -> Any:
return self.handler.send_request(method, params)
def implementation(self, params: lsp_types.ImplementationParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]:
"""A request to resolve the implementation locations of a symbol at a given text
@@ -13,7 +19,7 @@ class LanguageServerRequest:
(#TextDocumentPositionParams) the response is of type {@link Definition} or a
Thenable that resolves to such.
"""
return self.send_request("textDocument/implementation", params)
return self._send_request("textDocument/implementation", params)
def type_definition(
self, params: lsp_types.TypeDefinitionParams
@@ -23,7 +29,7 @@ class LanguageServerRequest:
(#TextDocumentPositionParams) the response is of type {@link Definition} or a
Thenable that resolves to such.
"""
return self.send_request("textDocument/typeDefinition", params)
return self._send_request("textDocument/typeDefinition", params)
def document_color(self, params: lsp_types.DocumentColorParams) -> list["lsp_types.ColorInformation"]:
"""A request to list all color symbols found in a given text document. The request's
@@ -31,7 +37,7 @@ class LanguageServerRequest:
response is of type {@link ColorInformation ColorInformation[]} or a Thenable
that resolves to such.
"""
return self.send_request("textDocument/documentColor", params)
return self._send_request("textDocument/documentColor", params)
def color_presentation(self, params: lsp_types.ColorPresentationParams) -> list["lsp_types.ColorPresentation"]:
"""A request to list all presentation for a color. The request's
@@ -39,7 +45,7 @@ class LanguageServerRequest:
response is of type {@link ColorInformation ColorInformation[]} or a Thenable
that resolves to such.
"""
return self.send_request("textDocument/colorPresentation", params)
return self._send_request("textDocument/colorPresentation", params)
def folding_range(self, params: lsp_types.FoldingRangeParams) -> list["lsp_types.FoldingRange"] | None:
"""A request to provide folding ranges in a document. The request's
@@ -47,7 +53,7 @@ class LanguageServerRequest:
response is of type {@link FoldingRangeList} or a Thenable
that resolves to such.
"""
return self.send_request("textDocument/foldingRange", params)
return self._send_request("textDocument/foldingRange", params)
def declaration(self, params: lsp_types.DeclarationParams) -> Union["lsp_types.Declaration", list["lsp_types.LocationLink"], None]:
"""A request to resolve the type definition locations of a symbol at a given text
@@ -56,7 +62,7 @@ class LanguageServerRequest:
or a typed array of {@link DeclarationLink} or a Thenable that resolves
to such.
"""
return self.send_request("textDocument/declaration", params)
return self._send_request("textDocument/declaration", params)
def selection_range(self, params: lsp_types.SelectionRangeParams) -> list["lsp_types.SelectionRange"] | None:
"""A request to provide selection ranges in a document. The request's
@@ -64,7 +70,7 @@ class LanguageServerRequest:
response is of type {@link SelectionRange SelectionRange[]} or a Thenable
that resolves to such.
"""
return self.send_request("textDocument/selectionRange", params)
return self._send_request("textDocument/selectionRange", params)
def prepare_call_hierarchy(self, params: lsp_types.CallHierarchyPrepareParams) -> list["lsp_types.CallHierarchyItem"] | None:
"""A request to result a `CallHierarchyItem` in a document at a given position.
@@ -72,42 +78,42 @@ class LanguageServerRequest:
@since 3.16.0
"""
return self.send_request("textDocument/prepareCallHierarchy", params)
return self._send_request("textDocument/prepareCallHierarchy", params)
def incoming_calls(self, params: lsp_types.CallHierarchyIncomingCallsParams) -> list["lsp_types.CallHierarchyIncomingCall"] | None:
"""A request to resolve the incoming calls for a given `CallHierarchyItem`.
@since 3.16.0
"""
return self.send_request("callHierarchy/incomingCalls", params)
return self._send_request("callHierarchy/incomingCalls", params)
def outgoing_calls(self, params: lsp_types.CallHierarchyOutgoingCallsParams) -> list["lsp_types.CallHierarchyOutgoingCall"] | None:
"""A request to resolve the outgoing calls for a given `CallHierarchyItem`.
@since 3.16.0
"""
return self.send_request("callHierarchy/outgoingCalls", params)
return self._send_request("callHierarchy/outgoingCalls", params)
def semantic_tokens_full(self, params: lsp_types.SemanticTokensParams) -> Union["lsp_types.SemanticTokens", None]:
"""@since 3.16.0"""
return self.send_request("textDocument/semanticTokens/full", params)
return self._send_request("textDocument/semanticTokens/full", params)
def semantic_tokens_delta(
self, params: lsp_types.SemanticTokensDeltaParams
) -> Union["lsp_types.SemanticTokens", "lsp_types.SemanticTokensDelta", None]:
"""@since 3.16.0"""
return self.send_request("textDocument/semanticTokens/full/delta", params)
return self._send_request("textDocument/semanticTokens/full/delta", params)
def semantic_tokens_range(self, params: lsp_types.SemanticTokensRangeParams) -> Union["lsp_types.SemanticTokens", None]:
"""@since 3.16.0"""
return self.send_request("textDocument/semanticTokens/range", params)
return self._send_request("textDocument/semanticTokens/range", params)
def linked_editing_range(self, params: lsp_types.LinkedEditingRangeParams) -> Union["lsp_types.LinkedEditingRanges", None]:
"""A request to provide ranges that can be edited together.
@since 3.16.0
"""
return self.send_request("textDocument/linkedEditingRange", params)
return self._send_request("textDocument/linkedEditingRange", params)
def will_create_files(self, params: lsp_types.CreateFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
"""The will create files request is sent from the client to the server before files are actually
@@ -115,7 +121,7 @@ class LanguageServerRequest:
@since 3.16.0
"""
return self.send_request("workspace/willCreateFiles", params)
return self._send_request("workspace/willCreateFiles", params)
def will_rename_files(self, params: lsp_types.RenameFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
"""The will rename files request is sent from the client to the server before files are actually
@@ -123,7 +129,7 @@ class LanguageServerRequest:
@since 3.16.0
"""
return self.send_request("workspace/willRenameFiles", params)
return self._send_request("workspace/willRenameFiles", params)
def will_delete_files(self, params: lsp_types.DeleteFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
"""The did delete files notification is sent from the client to the server when
@@ -131,14 +137,14 @@ class LanguageServerRequest:
@since 3.16.0
"""
return self.send_request("workspace/willDeleteFiles", params)
return self._send_request("workspace/willDeleteFiles", params)
def moniker(self, params: lsp_types.MonikerParams) -> list["lsp_types.Moniker"] | None:
"""A request to get the moniker of a symbol at a given text document position.
The request parameter is of type {@link TextDocumentPositionParams}.
The response is of type {@link Moniker Moniker[]} or `null`.
"""
return self.send_request("textDocument/moniker", params)
return self._send_request("textDocument/moniker", params)
def prepare_type_hierarchy(self, params: lsp_types.TypeHierarchyPrepareParams) -> list["lsp_types.TypeHierarchyItem"] | None:
"""A request to result a `TypeHierarchyItem` in a document at a given position.
@@ -146,21 +152,21 @@ class LanguageServerRequest:
@since 3.17.0
"""
return self.send_request("textDocument/prepareTypeHierarchy", params)
return self._send_request("textDocument/prepareTypeHierarchy", params)
def type_hierarchy_supertypes(self, params: lsp_types.TypeHierarchySupertypesParams) -> list["lsp_types.TypeHierarchyItem"] | None:
"""A request to resolve the supertypes for a given `TypeHierarchyItem`.
@since 3.17.0
"""
return self.send_request("typeHierarchy/supertypes", params)
return self._send_request("typeHierarchy/supertypes", params)
def type_hierarchy_subtypes(self, params: lsp_types.TypeHierarchySubtypesParams) -> list["lsp_types.TypeHierarchyItem"] | None:
"""A request to resolve the subtypes for a given `TypeHierarchyItem`.
@since 3.17.0
"""
return self.send_request("typeHierarchy/subtypes", params)
return self._send_request("typeHierarchy/subtypes", params)
def inline_value(self, params: lsp_types.InlineValueParams) -> list["lsp_types.InlineValue"] | None:
"""A request to provide inline values in a document. The request's parameter is of
@@ -169,7 +175,7 @@ class LanguageServerRequest:
@since 3.17.0
"""
return self.send_request("textDocument/inlineValue", params)
return self._send_request("textDocument/inlineValue", params)
def inlay_hint(self, params: lsp_types.InlayHintParams) -> list["lsp_types.InlayHint"] | None:
"""A request to provide inlay hints in a document. The request's parameter is of
@@ -178,7 +184,7 @@ class LanguageServerRequest:
@since 3.17.0
"""
return self.send_request("textDocument/inlayHint", params)
return self._send_request("textDocument/inlayHint", params)
def resolve_inlay_hint(self, params: lsp_types.InlayHint) -> "lsp_types.InlayHint":
"""A request to resolve additional properties for an inlay hint.
@@ -187,21 +193,21 @@ class LanguageServerRequest:
@since 3.17.0
"""
return self.send_request("inlayHint/resolve", params)
return self._send_request("inlayHint/resolve", params)
def text_document_diagnostic(self, params: lsp_types.DocumentDiagnosticParams) -> "lsp_types.DocumentDiagnosticReport":
"""The document diagnostic request definition.
@since 3.17.0
"""
return self.send_request("textDocument/diagnostic", params)
return self._send_request("textDocument/diagnostic", params)
def workspace_diagnostic(self, params: lsp_types.WorkspaceDiagnosticParams) -> "lsp_types.WorkspaceDiagnosticReport":
"""The workspace diagnostic request definition.
@since 3.17.0
"""
return self.send_request("workspace/diagnostic", params)
return self._send_request("workspace/diagnostic", params)
def initialize(self, params: lsp_types.InitializeParams) -> "lsp_types.InitializeResult":
"""The initialize request is sent from the client to the server.
@@ -210,7 +216,7 @@ class LanguageServerRequest:
the response if of type {@link InitializeResult} of a Thenable that
resolves to such.
"""
return self.send_request("initialize", params)
return self._send_request("initialize", params)
def shutdown(self) -> None:
"""A shutdown request is sent from the client to the server.
@@ -218,7 +224,7 @@ class LanguageServerRequest:
server. The only notification that is sent after a shutdown request
is the exit event.
"""
return self.send_request("shutdown")
return self._send_request("shutdown")
def will_save_wait_until(self, params: lsp_types.WillSaveTextDocumentParams) -> list["lsp_types.TextEdit"] | None:
"""A document will save request is sent from the client to the server before
@@ -228,7 +234,7 @@ class LanguageServerRequest:
server constantly fails on this request. This is done to keep the save fast and
reliable.
"""
return self.send_request("textDocument/willSaveWaitUntil", params)
return self._send_request("textDocument/willSaveWaitUntil", params)
def completion(self, params: lsp_types.CompletionParams) -> Union[list["lsp_types.CompletionItem"], "lsp_types.CompletionList", None]:
"""Request to request completion at a given text document position. The request's
@@ -241,24 +247,24 @@ class LanguageServerRequest:
request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
`filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
"""
return self.send_request("textDocument/completion", params)
return self._send_request("textDocument/completion", params)
def resolve_completion_item(self, params: lsp_types.CompletionItem) -> "lsp_types.CompletionItem":
"""Request to resolve additional information for a given completion item.The request's
parameter is of type {@link CompletionItem} the response
is of type {@link CompletionItem} or a Thenable that resolves to such.
"""
return self.send_request("completionItem/resolve", params)
return self._send_request("completionItem/resolve", params)
def hover(self, params: lsp_types.HoverParams) -> Union["lsp_types.Hover", None]:
"""Request to request hover information at a given text document position. The request's
parameter is of type {@link TextDocumentPosition} the response is of
type {@link Hover} or a Thenable that resolves to such.
"""
return self.send_request("textDocument/hover", params)
return self._send_request("textDocument/hover", params)
def signature_help(self, params: lsp_types.SignatureHelpParams) -> Union["lsp_types.SignatureHelp", None]:
return self.send_request("textDocument/signatureHelp", params)
return self._send_request("textDocument/signatureHelp", params)
def definition(self, params: lsp_types.DefinitionParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]:
"""A request to resolve the definition location of a symbol at a given text
@@ -267,7 +273,7 @@ class LanguageServerRequest:
or a typed array of {@link DefinitionLink} or a Thenable that resolves
to such.
"""
return self.send_request("textDocument/definition", params)
return self._send_request("textDocument/definition", params)
def references(self, params: lsp_types.ReferenceParams) -> list["lsp_types.Location"] | None:
"""A request to resolve project-wide references for the symbol denoted
@@ -275,7 +281,7 @@ class LanguageServerRequest:
type {@link ReferenceParams} the response is of type
{@link Location Location[]} or a Thenable that resolves to such.
"""
return self.send_request("textDocument/references", params)
return self._send_request("textDocument/references", params)
def document_highlight(self, params: lsp_types.DocumentHighlightParams) -> list["lsp_types.DocumentHighlight"] | None:
"""Request to resolve a {@link DocumentHighlight} for a given
@@ -283,7 +289,7 @@ class LanguageServerRequest:
(#TextDocumentPosition) the request response is of type [DocumentHighlight[]]
(#DocumentHighlight) or a Thenable that resolves to such.
"""
return self.send_request("textDocument/documentHighlight", params)
return self._send_request("textDocument/documentHighlight", params)
def document_symbol(
self, params: lsp_types.DocumentSymbolParams
@@ -293,18 +299,18 @@ class LanguageServerRequest:
response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
that resolves to such.
"""
return self.send_request("textDocument/documentSymbol", params)
return self._send_request("textDocument/documentSymbol", params)
def code_action(self, params: lsp_types.CodeActionParams) -> list[Union["lsp_types.Command", "lsp_types.CodeAction"]] | None:
"""A request to provide commands for the given text document and range."""
return self.send_request("textDocument/codeAction", params)
return self._send_request("textDocument/codeAction", params)
def resolve_code_action(self, params: lsp_types.CodeAction) -> "lsp_types.CodeAction":
"""Request to resolve additional information for a given code action.The request's
parameter is of type {@link CodeAction} the response
is of type {@link CodeAction} or a Thenable that resolves to such.
"""
return self.send_request("codeAction/resolve", params)
return self._send_request("codeAction/resolve", params)
def workspace_symbol(
self, params: lsp_types.WorkspaceSymbolParams
@@ -318,7 +324,7 @@ class LanguageServerRequest:
need to advertise support for WorkspaceSymbols via the client capability
`workspace.symbol.resolveSupport`.
"""
return self.send_request("workspace/symbol", params)
return self._send_request("workspace/symbol", params)
def resolve_workspace_symbol(self, params: lsp_types.WorkspaceSymbol) -> "lsp_types.WorkspaceSymbol":
"""A request to resolve the range inside the workspace
@@ -326,52 +332,52 @@ class LanguageServerRequest:
@since 3.17.0
"""
return self.send_request("workspaceSymbol/resolve", params)
return self._send_request("workspaceSymbol/resolve", params)
def code_lens(self, params: lsp_types.CodeLensParams) -> list["lsp_types.CodeLens"] | None:
"""A request to provide code lens for the given text document."""
return self.send_request("textDocument/codeLens", params)
return self._send_request("textDocument/codeLens", params)
def resolve_code_lens(self, params: lsp_types.CodeLens) -> "lsp_types.CodeLens":
"""A request to resolve a command for a given code lens."""
return self.send_request("codeLens/resolve", params)
return self._send_request("codeLens/resolve", params)
def document_link(self, params: lsp_types.DocumentLinkParams) -> list["lsp_types.DocumentLink"] | None:
"""A request to provide document links"""
return self.send_request("textDocument/documentLink", params)
return self._send_request("textDocument/documentLink", params)
def resolve_document_link(self, params: lsp_types.DocumentLink) -> "lsp_types.DocumentLink":
"""Request to resolve additional information for a given document link. The request's
parameter is of type {@link DocumentLink} the response
is of type {@link DocumentLink} or a Thenable that resolves to such.
"""
return self.send_request("documentLink/resolve", params)
return self._send_request("documentLink/resolve", params)
def formatting(self, params: lsp_types.DocumentFormattingParams) -> list["lsp_types.TextEdit"] | None:
"""A request to to format a whole document."""
return self.send_request("textDocument/formatting", params)
return self._send_request("textDocument/formatting", params)
def range_formatting(self, params: lsp_types.DocumentRangeFormattingParams) -> list["lsp_types.TextEdit"] | None:
"""A request to to format a range in a document."""
return self.send_request("textDocument/rangeFormatting", params)
return self._send_request("textDocument/rangeFormatting", params)
def on_type_formatting(self, params: lsp_types.DocumentOnTypeFormattingParams) -> list["lsp_types.TextEdit"] | None:
"""A request to format a document on type."""
return self.send_request("textDocument/onTypeFormatting", params)
return self._send_request("textDocument/onTypeFormatting", params)
def rename(self, params: lsp_types.RenameParams) -> Union["lsp_types.WorkspaceEdit", None]:
"""A request to rename a symbol."""
return self.send_request("textDocument/rename", params)
return self._send_request("textDocument/rename", params)
def prepare_rename(self, params: lsp_types.PrepareRenameParams) -> Union["lsp_types.PrepareRenameResult", None]:
"""A request to test and perform the setup necessary for a rename.
@since 3.16 - support for default behavior
"""
return self.send_request("textDocument/prepareRename", params)
return self._send_request("textDocument/prepareRename", params)
def execute_command(self, params: lsp_types.ExecuteCommandParams) -> Union["lsp_types.LSPAny", None]:
"""A request send from the client to the server to execute a command. The request might return
a workspace edit which the client will apply to the workspace.
"""
return self.send_request("workspace/executeCommand", params)
return self._send_request("workspace/executeCommand", params)
+11 -11
View File
@@ -14,7 +14,7 @@ from pathlib import Path, PurePath
import requests
from solidlsp.ls_exceptions import LanguageServerException
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_types import UnifiedSymbolInformation
@@ -170,13 +170,13 @@ class FileUtils:
"""
if not os.path.exists(file_path):
logger.log(f"File read '{file_path}' failed: File does not exist.", logging.ERROR)
raise LanguageServerException(f"File read '{file_path}' failed: File does not exist.")
raise SolidLSPException(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 LanguageServerException("File read failed.") from None
raise SolidLSPException("File read failed.") from None
@staticmethod
def download_file(logger: LanguageServerLogger, url: str, target_path: str) -> None:
@@ -188,12 +188,12 @@ 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 LanguageServerException("Error downloading file.")
raise SolidLSPException("Error downloading file.")
with open(target_path, "wb") as f:
shutil.copyfileobj(response.raw, f)
except Exception as exc:
logger.log(f"Error downloading file '{url}': {exc}", logging.ERROR)
raise LanguageServerException("Error downloading file.") from None
raise SolidLSPException("Error downloading file.") from None
@staticmethod
def download_and_extract_archive(logger: LanguageServerLogger, url: str, target_path: str, archive_type: str) -> None:
@@ -221,10 +221,10 @@ class FileUtils:
shutil.copyfileobj(f_in, f_out)
else:
logger.log(f"Unknown archive type '{archive_type}' for extraction", logging.ERROR)
raise LanguageServerException(f"Unknown archive type '{archive_type}'")
raise SolidLSPException(f"Unknown archive type '{archive_type}'")
except Exception as exc:
logger.log(f"Error extracting archive '{tmp_file_name}' obtained from '{url}': {exc}", logging.ERROR)
raise LanguageServerException("Error extracting archive.") from exc
raise SolidLSPException("Error extracting archive.") from exc
finally:
for tmp_file_name in tmp_files:
if os.path.exists(tmp_file_name):
@@ -290,7 +290,7 @@ class PlatformUtils:
platform_id += "-" + libc
return PlatformId(platform_id)
else:
raise LanguageServerException(f"Unknown platform: {system=}, {machine=}, {bitness=}")
raise SolidLSPException(f"Unknown platform: {system=}, {machine=}, {bitness=}")
@staticmethod
def _determine_windows_machine_type():
@@ -346,7 +346,7 @@ class PlatformUtils:
available_version_cmd_output.append(version_cmd_output)
if not available_version_cmd_output:
raise LanguageServerException("dotnet not found on the system")
raise SolidLSPException("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:
@@ -362,7 +362,7 @@ class PlatformUtils:
return DotnetVersion.V4
# If no supported version found, raise exception with all available versions
raise LanguageServerException(
raise SolidLSPException(
f"No supported dotnet version found. Available versions: {', '.join(available_version_cmd_output)}. Supported versions: 4, 6, 7, 8"
)
except (FileNotFoundError, subprocess.CalledProcessError):
@@ -370,7 +370,7 @@ class PlatformUtils:
result = subprocess.run(["mono", "--version"], capture_output=True, check=True)
return DotnetVersion.VMONO
except (FileNotFoundError, subprocess.CalledProcessError):
raise LanguageServerException("dotnet or mono not found on the system")
raise SolidLSPException("dotnet or mono not found on the system")
class SymbolUtils:
+4 -4
View File
@@ -59,7 +59,7 @@ class ProcessLaunchInfo:
cwd: str = os.getcwd()
class Error(Exception):
class LSPError(Exception):
def __init__(self, code: ErrorCodes, message: str) -> None:
super().__init__(message)
self.code = code
@@ -68,8 +68,8 @@ class Error(Exception):
return {"code": self.code, "message": super().__str__()}
@classmethod
def from_lsp(cls, d: StringDict) -> "Error":
return Error(d["code"], d["message"])
def from_lsp(cls, d: StringDict) -> "LSPError":
return LSPError(d["code"], d["message"])
def __str__(self) -> str:
return f"{super().__str__()} ({self.code})"
@@ -79,7 +79,7 @@ def make_response(request_id: Any, params: PayloadLike) -> StringDict:
return {"jsonrpc": "2.0", "id": request_id, "result": params}
def make_error_response(request_id: Any, err: Error) -> StringDict:
def make_error_response(request_id: Any, err: LSPError) -> StringDict:
return {"jsonrpc": "2.0", "id": request_id, "error": err.to_lsp()}