Document LSP crate
Co-Authored-By: Thorsten Ball <thorsten@zed.dev>
This commit is contained in:
parent
df67917768
commit
d7503a7d47
1 changed files with 58 additions and 9 deletions
|
@ -39,6 +39,7 @@ type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppCon
|
||||||
type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
|
type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
|
||||||
type IoHandler = Box<dyn Send + FnMut(IoKind, &str)>;
|
type IoHandler = Box<dyn Send + FnMut(IoKind, &str)>;
|
||||||
|
|
||||||
|
/// Kind of language server stdio given to an IO handler.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum IoKind {
|
pub enum IoKind {
|
||||||
StdOut,
|
StdOut,
|
||||||
|
@ -46,12 +47,15 @@ pub enum IoKind {
|
||||||
StdErr,
|
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)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct LanguageServerBinary {
|
pub struct LanguageServerBinary {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub arguments: Vec<OsString>,
|
pub arguments: Vec<OsString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A running language server process.
|
||||||
pub struct LanguageServer {
|
pub struct LanguageServer {
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
next_id: AtomicUsize,
|
next_id: AtomicUsize,
|
||||||
|
@ -70,10 +74,12 @@ pub struct LanguageServer {
|
||||||
_server: Option<Mutex<Child>>,
|
_server: Option<Mutex<Child>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Identifies a running language server.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct LanguageServerId(pub usize);
|
pub struct LanguageServerId(pub usize);
|
||||||
|
|
||||||
|
/// Handle to a language server RPC activity subscription.
|
||||||
pub enum Subscription {
|
pub enum Subscription {
|
||||||
Notification {
|
Notification {
|
||||||
method: &'static str,
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Request<'a, T> {
|
pub struct Request<'a, T> {
|
||||||
jsonrpc: &'static str,
|
jsonrpc: &'static str,
|
||||||
|
@ -93,6 +102,7 @@ pub struct Request<'a, T> {
|
||||||
params: T,
|
params: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Language server protocol RPC request response message before it is deserialized into a concrete type.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct AnyResponse<'a> {
|
struct AnyResponse<'a> {
|
||||||
jsonrpc: &'a str,
|
jsonrpc: &'a str,
|
||||||
|
@ -103,6 +113,9 @@ struct AnyResponse<'a> {
|
||||||
result: Option<&'a RawValue>,
|
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)]
|
#[derive(Serialize)]
|
||||||
struct Response<T> {
|
struct Response<T> {
|
||||||
jsonrpc: &'static str,
|
jsonrpc: &'static str,
|
||||||
|
@ -111,6 +124,9 @@ struct Response<T> {
|
||||||
error: Option<Error>,
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct Notification<'a, T> {
|
struct Notification<'a, T> {
|
||||||
jsonrpc: &'static str,
|
jsonrpc: &'static str,
|
||||||
|
@ -119,6 +135,7 @@ struct Notification<'a, T> {
|
||||||
params: T,
|
params: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Language server RPC notification message before it is deserialized into a concrete type.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
struct AnyNotification<'a> {
|
struct AnyNotification<'a> {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -135,6 +152,7 @@ struct Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
|
/// Starts a language server process.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
stderr_capture: Arc<Mutex<Option<String>>>,
|
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||||
server_id: LanguageServerId,
|
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>> {
|
pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
||||||
self.code_action_kinds.clone()
|
self.code_action_kinds.clone()
|
||||||
}
|
}
|
||||||
|
@ -427,9 +446,10 @@ impl LanguageServer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes a language server.
|
/// Initializes a language server by sending the `Initialize` request.
|
||||||
/// Note that `options` is used directly to construct [`InitializeParams`],
|
/// Note that `options` is used directly to construct [`InitializeParams`], which is why it is owned.
|
||||||
/// 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>> {
|
pub async fn initialize(mut self, options: Option<Value>) -> Result<Arc<Self>> {
|
||||||
let root_uri = Url::from_file_path(&self.root_path).unwrap();
|
let root_uri = Url::from_file_path(&self.root_path).unwrap();
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
|
@ -564,6 +584,7 @@ impl LanguageServer {
|
||||||
Ok(Arc::new(self))
|
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<()>>> {
|
pub fn shutdown(&self) -> Option<impl 'static + Send + Future<Output = Option<()>>> {
|
||||||
if let Some(tasks) = self.io_tasks.lock().take() {
|
if let Some(tasks) = self.io_tasks.lock().take() {
|
||||||
let response_handlers = self.response_handlers.clone();
|
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]
|
#[must_use]
|
||||||
pub fn on_notification<T, F>(&self, f: F) -> Subscription
|
pub fn on_notification<T, F>(&self, f: F) -> Subscription
|
||||||
where
|
where
|
||||||
|
@ -607,6 +631,9 @@ impl LanguageServer {
|
||||||
self.on_custom_notification(T::METHOD, f)
|
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]
|
#[must_use]
|
||||||
pub fn on_request<T, F, Fut>(&self, f: F) -> Subscription
|
pub fn on_request<T, F, Fut>(&self, f: F) -> Subscription
|
||||||
where
|
where
|
||||||
|
@ -618,6 +645,7 @@ impl LanguageServer {
|
||||||
self.on_custom_request(T::METHOD, f)
|
self.on_custom_request(T::METHOD, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a handler to inspect all language server process stdio.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_io<F>(&self, f: F) -> Subscription
|
pub fn on_io<F>(&self, f: F) -> Subscription
|
||||||
where
|
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) {
|
pub fn remove_request_handler<T: request::Request>(&self) {
|
||||||
self.notification_handlers.lock().remove(T::METHOD);
|
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) {
|
pub fn remove_notification_handler<T: notification::Notification>(&self) {
|
||||||
self.notification_handlers.lock().remove(T::METHOD);
|
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 {
|
pub fn has_notification_handler<T: notification::Notification>(&self) -> bool {
|
||||||
self.notification_handlers.lock().contains_key(T::METHOD)
|
self.notification_handlers.lock().contains_key(T::METHOD)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[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
|
where
|
||||||
F: 'static + FnMut(Params, AsyncAppContext) + Send,
|
F: 'static + FnMut(Params, AsyncAppContext) + Send,
|
||||||
Params: DeserializeOwned,
|
Params: DeserializeOwned,
|
||||||
|
@ -668,11 +699,7 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_custom_request<Params, Res, Fut, F>(
|
fn on_custom_request<Params, Res, Fut, F>(&self, method: &'static str, mut f: F) -> Subscription
|
||||||
&self,
|
|
||||||
method: &'static str,
|
|
||||||
mut f: F,
|
|
||||||
) -> Subscription
|
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send,
|
F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send,
|
||||||
Fut: 'static + Future<Output = Result<Res>>,
|
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 {
|
pub fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the reported capabilities of the running language server.
|
||||||
pub fn capabilities(&self) -> &ServerCapabilities {
|
pub fn capabilities(&self) -> &ServerCapabilities {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the id of the running language server.
|
||||||
pub fn server_id(&self) -> LanguageServerId {
|
pub fn server_id(&self) -> LanguageServerId {
|
||||||
self.server_id
|
self.server_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the root path of the project the language server is running against.
|
||||||
pub fn root_path(&self) -> &PathBuf {
|
pub fn root_path(&self) -> &PathBuf {
|
||||||
&self.root_path
|
&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>(
|
pub fn request<T: request::Request>(
|
||||||
&self,
|
&self,
|
||||||
params: T::Params,
|
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<()> {
|
pub fn notify<T: notification::Notification>(&self, params: T::Params) -> Result<()> {
|
||||||
Self::notify_internal::<T>(&self.outbound_tx, params)
|
Self::notify_internal::<T>(&self.outbound_tx, params)
|
||||||
}
|
}
|
||||||
|
@ -879,6 +916,7 @@ impl Drop for LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subscription {
|
impl Subscription {
|
||||||
|
/// Detaching a subscription handle prevents it from unsubscribing on drop.
|
||||||
pub fn detach(&mut self) {
|
pub fn detach(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
Subscription::Notification {
|
Subscription::Notification {
|
||||||
|
@ -925,6 +963,7 @@ impl Drop for Subscription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mock language server for use in tests.
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FakeLanguageServer {
|
pub struct FakeLanguageServer {
|
||||||
|
@ -946,6 +985,7 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a fake language server.
|
||||||
pub fn fake(
|
pub fn fake(
|
||||||
name: String,
|
name: String,
|
||||||
capabilities: ServerCapabilities,
|
capabilities: ServerCapabilities,
|
||||||
|
@ -1015,10 +1055,12 @@ impl LanguageServer {
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
impl FakeLanguageServer {
|
impl FakeLanguageServer {
|
||||||
|
/// See [LanguageServer::notify]
|
||||||
pub fn notify<T: notification::Notification>(&self, params: T::Params) {
|
pub fn notify<T: notification::Notification>(&self, params: T::Params) {
|
||||||
self.server.notify::<T>(params).ok();
|
self.server.notify::<T>(params).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [LanguageServer::request]
|
||||||
pub async fn request<T>(&self, params: T::Params) -> Result<T::Result>
|
pub async fn request<T>(&self, params: T::Params) -> Result<T::Result>
|
||||||
where
|
where
|
||||||
T: request::Request,
|
T: request::Request,
|
||||||
|
@ -1028,11 +1070,13 @@ impl FakeLanguageServer {
|
||||||
self.server.request::<T>(params).await
|
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 {
|
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
|
||||||
self.server.executor.start_waiting();
|
self.server.executor.start_waiting();
|
||||||
self.try_receive_notification::<T>().await.unwrap()
|
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>(
|
pub async fn try_receive_notification<T: notification::Notification>(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Option<T::Params> {
|
) -> 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>(
|
pub fn handle_request<T, F, Fut>(
|
||||||
&self,
|
&self,
|
||||||
mut handler: F,
|
mut handler: F,
|
||||||
|
@ -1076,6 +1121,7 @@ impl FakeLanguageServer {
|
||||||
responded_rx
|
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>(
|
pub fn handle_notification<T, F>(
|
||||||
&self,
|
&self,
|
||||||
mut handler: F,
|
mut handler: F,
|
||||||
|
@ -1096,6 +1142,7 @@ impl FakeLanguageServer {
|
||||||
handled_rx
|
handled_rx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes any existing handler for specified notification type.
|
||||||
pub fn remove_request_handler<T>(&mut self)
|
pub fn remove_request_handler<T>(&mut self)
|
||||||
where
|
where
|
||||||
T: 'static + request::Request,
|
T: 'static + request::Request,
|
||||||
|
@ -1103,6 +1150,7 @@ impl FakeLanguageServer {
|
||||||
self.server.remove_request_handler::<T>();
|
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>) {
|
pub async fn start_progress(&self, token: impl Into<String>) {
|
||||||
let token = token.into();
|
let token = token.into();
|
||||||
self.request::<request::WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
|
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>) {
|
pub fn end_progress(&self, token: impl Into<String>) {
|
||||||
self.notify::<notification::Progress>(ProgressParams {
|
self.notify::<notification::Progress>(ProgressParams {
|
||||||
token: NumberOrString::String(token.into()),
|
token: NumberOrString::String(token.into()),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue