Skip to main content

LspClient

Struct LspClient 

Source
pub struct LspClient { /* private fields */ }
Expand description

LSP client with async request/response handling.

This client manages communication with an LSP server, handling:

  • Concurrent requests with unique ID tracking
  • Background message loop for receiving responses
  • Timeout support for all requests
  • Graceful shutdown

Implementations§

Source§

impl LspClient

Source

pub fn new(config: LspServerConfig) -> Self

Create a new LSP client with the given configuration.

The client starts in an uninitialized state. Call initialize() to start the server and complete the initialization handshake.

Source

pub const fn language_id(&self) -> &LanguageId

Get the language ID for this client.

Source

pub async fn state(&self) -> ServerState

Get the current server state.

Source

pub const fn request_timeout(&self) -> Duration

The timeout applied to a single LSP request attempt, derived from LspServerConfig::request_timeout_seconds.

This bounds one attempt, not a whole tool call: Self::request retries up to SERVER_CANCELLED_MAX_RETRIES (3) additional times on a -32802 (ServerCancelled) or -32801 (ContentModified) response, sharing one attempt budget between the two codes, so the worst-case latency for a single tool call is 4 * request_timeout() + 3.5s (the sum of the retry backoff delays).

Exception: get_code_actions (bridge::translator::edits) can add a second, concurrent round of requests on top of this bound – up to MAX_CODE_ACTION_RESOLVES codeAction/resolve calls, each retried under the same rules but bounded by Self::code_action_resolve_timeout rather than this timeout. Since those run concurrently with each other (not with the initial textDocument/codeAction request), the worst case for that one tool call is (4 * request_timeout() + 3.5s) + (4 * code_action_resolve_timeout() + 3.5s), not a multiple scaling with the number of resolved actions.

The value is a TimeoutSecs, so it is already within 1 second to MAX_TIMEOUT_SECONDS and needs no clamping here.

§Examples
use std::time::Duration;
use mcpls_core::config::{LspServerConfig, TimeoutSecs};
use mcpls_core::lsp::LspClient;

let mut config = LspServerConfig::rust_analyzer();
config.request_timeout_seconds = TimeoutSecs::new(45).unwrap();
let client = LspClient::new(config);

assert_eq!(client.request_timeout(), Duration::from_secs(45));
Source

pub fn completion_timeout(&self) -> Duration

The timeout applied to completion (textDocument/completion) requests.

Equal to Self::request_timeout, capped at 10 seconds. Completions cannot be configured above this cap by any value of request_timeout_seconds — if that proves insufficient in practice, the fix is a dedicated completion_timeout_seconds field, not raising this cap.

§Examples
use std::time::Duration;
use mcpls_core::config::{LspServerConfig, TimeoutSecs};
use mcpls_core::lsp::LspClient;

let mut config = LspServerConfig::rust_analyzer();
config.request_timeout_seconds = TimeoutSecs::new(300).unwrap();
let client = LspClient::new(config);

// Capped at 10s even though request_timeout_seconds is 300.
assert_eq!(client.completion_timeout(), Duration::from_secs(10));
assert!(client.completion_timeout() <= client.request_timeout());
Source

pub fn code_action_resolve_timeout(&self) -> Duration

The timeout applied to a single codeAction/resolve request.

Equal to Self::request_timeout, capped at 10 seconds.

§Examples
use std::time::Duration;
use mcpls_core::config::{LspServerConfig, TimeoutSecs};
use mcpls_core::lsp::LspClient;

let mut config = LspServerConfig::rust_analyzer();
config.request_timeout_seconds = TimeoutSecs::new(300).unwrap();
let client = LspClient::new(config);

// Capped at 10s even though request_timeout_seconds is 300.
assert_eq!(client.code_action_resolve_timeout(), Duration::from_secs(10));
assert!(client.code_action_resolve_timeout() <= client.request_timeout());
Source

pub async fn request<P, R>( &self, method: &str, params: P, timeout_duration: Duration, ) -> Result<R>

Send request and wait for response with timeout.

Automatically retries up to 3 times when the server returns error code -32802 (ServerCancelled, any method) or -32801 (ContentModified, only for methods in CONTENT_MODIFIED_RETRY_METHODS) – gated by data.retriggerRequest when present – using exponential backoff starting at 500 ms. Both codes share the same attempt budget: a request that hits -32801 then -32802 does not get 8 attempts, only 4.

This method owns the severity of LSP error-response logging (#392): a transient error about to be retried logs at warn!, while error! is reserved for an error actually surfaced to the caller – retry exhaustion or a non-retryable code/method combination. This is deliberately not decided in message_loop_inner, which parses the response before knowing whether a retry will follow.

§Type Parameters
  • P - The type of the request parameters (must be serializable)
  • R - The type of the response result (must be deserializable)
§Errors

Returns an error if:

  • Server has shut down
  • Request times out
  • Response cannot be deserialized
  • LSP server returns an error
Source

pub async fn request_typed<R>( &self, params: R::Params, timeout_duration: Duration, ) -> Result<R::Result>
where R: Request,

Send a typed LSP request, deriving both the method string and the result type from R’s lsp_types::Request implementation so they cannot drift independently – unlike Self::request, which takes the method string and result type as two unchecked, hand-picked values.

§Errors

See Self::request.

Source

pub async fn notify<P>(&self, method: &str, params: P) -> Result<()>
where P: Serialize,

Send notification (fire-and-forget, no response expected).

§Errors

Returns an error if the server has shut down.

Source

pub async fn notify_typed<N>(&self, params: N::Params) -> Result<()>
where N: Notification,

Send a typed LSP notification, deriving the method string from N’s lsp_types::Notification implementation so it cannot drift from the params type – the notification counterpart to Self::request_typed.

§Errors

See Self::notify.

Source

pub async fn shutdown(self) -> Result<()>

Shutdown client gracefully, with a deadline of SHUTDOWN_TIMEOUT.

This sends a shutdown command to the background task and waits for it to complete. Only the client that owns the message loop (the one held by LspServer) can abort a wedged loop; calling this on a clone still stops the shared loop for every clone but cannot abort it.

§Errors

Returns Error::ShutdownTimeout if the loop did not stop in time (it is then aborted and every pending request fails with Error::ServerTerminated), or another error if the background task failed.

Trait Implementations§

Source§

impl Clone for LspClient

Source§

fn clone(&self) -> Self

Creates a clone that shares the underlying connection.

The clone does not own the receiver task and cannot perform shutdown. All clones share the same command channel for sending requests.

1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for LspClient

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more