Document LSP crate (#4098)

Release Notes:

- N/A
This commit is contained in:
Julia 2024-01-17 14:01:41 -05:00 committed by GitHub
commit e1194e0cac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -39,6 +39,7 @@ type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppCon
type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
type IoHandler = Box<dyn Send + FnMut(IoKind, &str)>;
/// Kind of language server stdio given to an IO handler.
#[derive(Debug, Clone, Copy)]
pub enum IoKind {
StdOut,
@ -46,12 +47,15 @@ pub enum IoKind {
StdErr,
}
/// Represents a launchable language server. This can either be a standalone binary or the path
/// to a runtime with arguments to instruct it to launch the actual language server file.
#[derive(Debug, Clone, Deserialize)]
pub struct LanguageServerBinary {
pub path: PathBuf,
pub arguments: Vec<OsString>,
}
/// A running language server process.
pub struct LanguageServer {
server_id: LanguageServerId,
next_id: AtomicUsize,
@ -70,10 +74,12 @@ pub struct LanguageServer {
_server: Option<Mutex<Child>>,
}
/// Identifies a running language server.
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct LanguageServerId(pub usize);
/// Handle to a language server RPC activity subscription.
pub enum Subscription {
Notification {
method: &'static str,
@ -85,6 +91,9 @@ pub enum Subscription {
},
}
/// Language server protocol RPC request message.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage)
#[derive(Serialize, Deserialize)]
pub struct Request<'a, T> {
jsonrpc: &'static str,
@ -93,6 +102,7 @@ pub struct Request<'a, T> {
params: T,
}
/// Language server protocol RPC request response message before it is deserialized into a concrete type.
#[derive(Serialize, Deserialize)]
struct AnyResponse<'a> {
jsonrpc: &'a str,
@ -103,6 +113,9 @@ struct AnyResponse<'a> {
result: Option<&'a RawValue>,
}
/// Language server protocol RPC request response message.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage)
#[derive(Serialize)]
struct Response<T> {
jsonrpc: &'static str,
@ -111,6 +124,9 @@ struct Response<T> {
error: Option<Error>,
}
/// Language server protocol RPC notification message.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage)
#[derive(Serialize, Deserialize)]
struct Notification<'a, T> {
jsonrpc: &'static str,
@ -119,6 +135,7 @@ struct Notification<'a, T> {
params: T,
}
/// Language server RPC notification message before it is deserialized into a concrete type.
#[derive(Debug, Clone, Deserialize)]
struct AnyNotification<'a> {
#[serde(default)]
@ -135,6 +152,7 @@ struct Error {
}
impl LanguageServer {
/// Starts a language server process.
pub fn new(
stderr_capture: Arc<Mutex<Option<String>>>,
server_id: LanguageServerId,
@ -277,6 +295,7 @@ impl LanguageServer {
}
}
/// List of code action kinds this language server reports being able to emit.
pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
self.code_action_kinds.clone()
}
@ -427,9 +446,10 @@ impl LanguageServer {
Ok(())
}
/// Initializes a language server.
/// Note that `options` is used directly to construct [`InitializeParams`],
/// which is why it is owned.
/// Initializes a language server by sending the `Initialize` request.
/// Note that `options` is used directly to construct [`InitializeParams`], which is why it is owned.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize)
pub async fn initialize(mut self, options: Option<Value>) -> Result<Arc<Self>> {
let root_uri = Url::from_file_path(&self.root_path).unwrap();
#[allow(deprecated)]
@ -564,6 +584,7 @@ impl LanguageServer {
Ok(Arc::new(self))
}
/// Sends a shutdown request to the language server process and prepares the `LanguageServer` to be dropped.
pub fn shutdown(&self) -> Option<impl 'static + Send + Future<Output = Option<()>>> {
if let Some(tasks) = self.io_tasks.lock().take() {
let response_handlers = self.response_handlers.clone();
@ -598,6 +619,9 @@ impl LanguageServer {
}
}
/// Register a handler to handle incoming LSP notifications.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage)
#[must_use]
pub fn on_notification<T, F>(&self, f: F) -> Subscription
where
@ -607,6 +631,9 @@ impl LanguageServer {
self.on_custom_notification(T::METHOD, f)
}
/// Register a handler to handle incoming LSP requests.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage)
#[must_use]
pub fn on_request<T, F, Fut>(&self, f: F) -> Subscription
where
@ -618,6 +645,7 @@ impl LanguageServer {
self.on_custom_request(T::METHOD, f)
}
/// Register a handler to inspect all language server process stdio.
#[must_use]
pub fn on_io<F>(&self, f: F) -> Subscription
where
@ -631,20 +659,23 @@ impl LanguageServer {
}
}
/// Removes a request handler registers via [Self::on_request].
pub fn remove_request_handler<T: request::Request>(&self) {
self.notification_handlers.lock().remove(T::METHOD);
}
/// Removes a notification handler registers via [Self::on_notification].
pub fn remove_notification_handler<T: notification::Notification>(&self) {
self.notification_handlers.lock().remove(T::METHOD);
}
/// Checks if a notification handler has been registered via [Self::on_notification].
pub fn has_notification_handler<T: notification::Notification>(&self) -> bool {
self.notification_handlers.lock().contains_key(T::METHOD)
}
#[must_use]
pub fn on_custom_notification<Params, F>(&self, method: &'static str, mut f: F) -> Subscription
fn on_custom_notification<Params, F>(&self, method: &'static str, mut f: F) -> Subscription
where
F: 'static + FnMut(Params, AsyncAppContext) + Send,
Params: DeserializeOwned,
@ -668,11 +699,7 @@ impl LanguageServer {
}
#[must_use]
pub fn on_custom_request<Params, Res, Fut, F>(
&self,
method: &'static str,
mut f: F,
) -> Subscription
fn on_custom_request<Params, Res, Fut, F>(&self, method: &'static str, mut f: F) -> Subscription
where
F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send,
Fut: 'static + Future<Output = Result<Res>>,
@ -750,22 +777,29 @@ impl LanguageServer {
}
}
/// Get the name of the running language server.
pub fn name(&self) -> &str {
&self.name
}
/// Get the reported capabilities of the running language server.
pub fn capabilities(&self) -> &ServerCapabilities {
&self.capabilities
}
/// Get the id of the running language server.
pub fn server_id(&self) -> LanguageServerId {
self.server_id
}
/// Get the root path of the project the language server is running against.
pub fn root_path(&self) -> &PathBuf {
&self.root_path
}
/// Sends a RPC request to the language server.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage)
pub fn request<T: request::Request>(
&self,
params: T::Params,
@ -851,6 +885,9 @@ impl LanguageServer {
}
}
/// Sends a RPC notification to the language server.
///
/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage)
pub fn notify<T: notification::Notification>(&self, params: T::Params) -> Result<()> {
Self::notify_internal::<T>(&self.outbound_tx, params)
}
@ -879,6 +916,7 @@ impl Drop for LanguageServer {
}
impl Subscription {
/// Detaching a subscription handle prevents it from unsubscribing on drop.
pub fn detach(&mut self) {
match self {
Subscription::Notification {
@ -925,6 +963,7 @@ impl Drop for Subscription {
}
}
/// Mock language server for use in tests.
#[cfg(any(test, feature = "test-support"))]
#[derive(Clone)]
pub struct FakeLanguageServer {
@ -946,6 +985,7 @@ impl LanguageServer {
}
}
/// Construct a fake language server.
pub fn fake(
name: String,
capabilities: ServerCapabilities,
@ -1015,10 +1055,12 @@ impl LanguageServer {
#[cfg(any(test, feature = "test-support"))]
impl FakeLanguageServer {
/// See [LanguageServer::notify]
pub fn notify<T: notification::Notification>(&self, params: T::Params) {
self.server.notify::<T>(params).ok();
}
/// See [LanguageServer::request]
pub async fn request<T>(&self, params: T::Params) -> Result<T::Result>
where
T: request::Request,
@ -1028,11 +1070,13 @@ impl FakeLanguageServer {
self.server.request::<T>(params).await
}
/// Attempts [try_receive_notification], unwrapping if it has not received the specified type yet.
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
self.server.executor.start_waiting();
self.try_receive_notification::<T>().await.unwrap()
}
/// Consumes the notification channel until it finds a notification for the specified type.
pub async fn try_receive_notification<T: notification::Notification>(
&mut self,
) -> Option<T::Params> {
@ -1048,6 +1092,7 @@ impl FakeLanguageServer {
}
}
/// Registers a handler for a specific kind of request. Removes any existing handler for specified request type.
pub fn handle_request<T, F, Fut>(
&self,
mut handler: F,
@ -1076,6 +1121,7 @@ impl FakeLanguageServer {
responded_rx
}
/// Registers a handler for a specific kind of notification. Removes any existing handler for specified notification type.
pub fn handle_notification<T, F>(
&self,
mut handler: F,
@ -1096,6 +1142,7 @@ impl FakeLanguageServer {
handled_rx
}
/// Removes any existing handler for specified notification type.
pub fn remove_request_handler<T>(&mut self)
where
T: 'static + request::Request,
@ -1103,6 +1150,7 @@ impl FakeLanguageServer {
self.server.remove_request_handler::<T>();
}
/// Simulate that the server has started work and notifies about its progress with the specified token.
pub async fn start_progress(&self, token: impl Into<String>) {
let token = token.into();
self.request::<request::WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
@ -1116,6 +1164,7 @@ impl FakeLanguageServer {
});
}
/// Simulate that the server has completed work and notifies about that with the specified token.
pub fn end_progress(&self, token: impl Into<String>) {
self.notify::<notification::Progress>(ProgressParams {
token: NumberOrString::String(token.into()),