mod input_handler; pub use lsp_types::request::*;pub use lsp_types::*; use anyhow::{Context as _, Result, anyhow};use collections::{BTreeMap, HashMap};use futures::{ AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, Future, FutureExt, StreamExt, channel::oneshot::{self, Canceled}, future::{self, Either}, io::{BufReader, BufWriter}, select,};use gpui::{App, AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Task};use notification::DidChangeWorkspaceFolders;use parking_lot::{Mutex, RwLock};use postage::{barrier, prelude::Stream};use schemars::JsonSchema;use serde::{Deserialize, Serialize, de::DeserializeOwned};use serde_json::{Value, json, value::RawValue};use util::command::{Child, Stdio}; use gpui_util::{ResultExt, TryFutureExt};use std::path::Path;use std::{ any::TypeId, collections::BTreeSet, ffi::{OsStr, OsString}, fmt, io::Write, ops::DerefMut, path::PathBuf, pin::Pin, sync::{ Arc, Weak, atomic::{AtomicI32, Ordering::SeqCst}, }, task::Poll, time::{Duration, Instant},};use util::{ConnectionResult, redact}; const JSON_RPC_VERSION: &str = "2.0";const CONTENT_LEN_HEADER: &str = "Content-Length: "; /// The default amount of time to wait while initializing or fetching LSP servers, in seconds.////// Should not be used (in favor of DEFAULT_LSP_REQUEST_TIMEOUT) and is exported solely for use inside ProjectSettings defaults.pub const DEFAULT_LSP_REQUEST_TIMEOUT_SECS: u64 = 120;/// A timeout representing the value of [DEFAULT_LSP_REQUEST_TIMEOUT_SECS].////// Should **only be used** in tests and as a fallback when a corresponding config value cannot be obtained!pub const DEFAULT_LSP_REQUEST_TIMEOUT: Duration = Duration::from_secs(DEFAULT_LSP_REQUEST_TIMEOUT_SECS); /// The shutdown timeout for LSP servers (including Prettier/Copilot).const SERVER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); pub fn workspace_folder_for_uri(uri: Uri) -> WorkspaceFolder { let name = uri .to_file_path() .ok() .map(|path| { let name = path.file_name().unwrap_or(path.as_os_str()); name.to_string_lossy().into_owned() }) .filter(|name| !name.is_empty()) .or_else(|| { uri.path_segments() .and_then(|mut segments| segments.rfind(|segment| !segment.is_empty())) .map(str::to_owned) }) .unwrap_or_else(|| uri.as_str().to_owned()); WorkspaceFolder { uri, name }} type NotificationHandler = Box<dyn Send + FnMut(Option<RequestId>, Value, &mut AsyncApp)>;type PendingRespondTasks = Arc<Mutex<HashMap<RequestId, Task<()>>>>;type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>) -> Task<()>>;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, StdIn, 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(Clone, Serialize)]pub struct LanguageServerBinary { pub path: PathBuf, pub arguments: Vec<OsString>, pub env: Option<HashMap<String, String>>,} /// Configures the search (and installation) of language servers.#[derive(Debug, Clone)]pub struct LanguageServerBinaryOptions { /// Whether the adapter should look at the users system pub allow_path_lookup: bool, /// Whether the adapter should download its own version pub allow_binary_download: bool, /// Whether the adapter should download a pre-release version pub pre_release: bool,} struct NotificationSerializer(Box<dyn FnOnce() -> String + Send + Sync>); /// A running language server process.pub struct LanguageServer { server_id: LanguageServerId, next_id: AtomicI32, outbound_tx: async_channel::Sender<String>, notification_tx: async_channel::Sender<NotificationSerializer>, name: LanguageServerName, version: Option<SharedString>, process_name: Arc<str>, binary: LanguageServerBinary, capabilities: RwLock<ServerCapabilities>, /// Configuration sent to the server, stored for display in the language server logs /// buffer. This is represented as the message sent to the LSP in order to avoid cloning it (can /// be large in cases like sending schemas to the json server). configuration: Arc<DidChangeConfigurationParams>, code_action_kinds: Option<Vec<CodeActionKind>>, notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>, response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>, /// Tasks spawned by `on_custom_request` to compute responses. Tracked so that /// incoming `$/cancelRequest` notifications can cancel them by dropping the task. pending_respond_tasks: PendingRespondTasks, io_handlers: Arc<Mutex<HashMap<i32, IoHandler>>>, executor: BackgroundExecutor, #[allow(clippy::type_complexity)] io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>, output_done_rx: Mutex<Option<barrier::Receiver>>, server: Arc<Mutex<Option<Child>>>, workspace_folders: Option<Arc<Mutex<BTreeSet<Uri>>>>, root_uri: Uri,} #[derive(Clone, Debug, PartialEq, Eq, Hash)]pub enum LanguageServerSelector { Id(LanguageServerId), Name(LanguageServerName),} /// Identifies a running language server.#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]#[repr(transparent)]pub struct LanguageServerId(pub usize); impl LanguageServerId { pub fn from_proto(id: u64) -> Self { Self(id as usize) } pub fn to_proto(self) -> u64 { self.0 as u64 }} /// A name of a language server.#[derive( Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, JsonSchema,)]#[serde(transparent)]pub struct LanguageServerName(pub SharedString); impl std::fmt::Display for LanguageServerName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, f) }} impl AsRef<str> for LanguageServerName { fn as_ref(&self) -> &str { self.0.as_ref() }} impl AsRef<OsStr> for LanguageServerName { fn as_ref(&self) -> &OsStr { self.0.as_ref().as_ref() }} impl LanguageServerName { pub const fn new_static(s: &'static str) -> Self { Self(SharedString::new_static(s)) } pub fn from_proto(s: String) -> Self { Self(s.into()) }} impl<'a> From<&'a str> for LanguageServerName { fn from(str: &'a str) -> LanguageServerName { LanguageServerName(str.to_string().into()) }} impl PartialEq<str> for LanguageServerName { fn eq(&self, other: &str) -> bool { self.0 == other }} /// Handle to a language server RPC activity subscription.pub enum Subscription { Notification { method: &'static str, notification_handlers: Option<Weak<Mutex<HashMap<&'static str, NotificationHandler>>>>, }, Io { id: i32, io_handlers: Option<Weak<Mutex<HashMap<i32, IoHandler>>>>, },} /// Language server protocol RPC request message ID.////// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage)#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]#[serde(untagged)]pub enum RequestId { Int(i32), Str(String),} fn is_unit<T: 'static>(_: &T) -> bool { TypeId::of::<T>() == TypeId::of::<()>()} /// 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>where T: 'static,{ jsonrpc: &'static str, id: RequestId, method: &'a str, #[serde(default, skip_serializing_if = "is_unit")] 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, id: RequestId, #[serde(default)] error: Option<Error>, #[serde(borrow)] 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, id: RequestId, #[serde(flatten)] value: LspResult<T>,} #[derive(Serialize)]#[serde(rename_all = "snake_case")]enum LspResult<T> { #[serde(rename = "result")] Ok(Option<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>where T: 'static,{ jsonrpc: &'static str, #[serde(borrow)] method: &'a str, #[serde(default, skip_serializing_if = "is_unit")] params: T,} /// Language server RPC notification message before it is deserialized into a concrete type.#[derive(Debug, Clone, Deserialize)]struct NotificationOrRequest { #[serde(default)] id: Option<RequestId>, method: String, #[serde(default)] params: Option<Value>,} #[derive(Debug, Serialize, Deserialize)]struct Error { code: i64, message: String, #[serde(default)] data: Option<serde_json::Value>,} pub trait LspRequestFuture<O>: Future<Output = ConnectionResult<O>> { fn id(&self) -> i32;} struct LspRequest<F> { id: i32, request: F,} impl<F> LspRequest<F> { pub fn new(id: i32, request: F) -> Self { Self { id, request } }} impl<F: Future> Future for LspRequest<F> { type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> { // SAFETY: This is standard pin projection, we're pinned so our fields must be pinned. let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().request) }; inner.poll(cx) }} impl<F, O> LspRequestFuture<O> for LspRequest<F>where F: Future<Output = ConnectionResult<O>>,{ fn id(&self) -> i32 { self.id }} /// Combined capabilities of the server and the adapter.#[derive(Debug, Clone)]pub struct AdapterServerCapabilities { // Reported capabilities by the server pub server_capabilities: ServerCapabilities, // List of code actions supported by the LspAdapter matching the server pub code_action_kinds: Option<Vec<CodeActionKind>>,} // See the VSCode docs [1] and the LSP Spec [2]//// [1]: https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide#standard-token-types-and-modifiers// [2]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokenTypespub const SEMANTIC_TOKEN_TYPES: &[SemanticTokenType] = &[ SemanticTokenType::NAMESPACE, SemanticTokenType::CLASS, SemanticTokenType::ENUM, SemanticTokenType::INTERFACE, SemanticTokenType::STRUCT, SemanticTokenType::TYPE_PARAMETER, SemanticTokenType::TYPE, SemanticTokenType::PARAMETER, SemanticTokenType::VARIABLE, SemanticTokenType::PROPERTY, SemanticTokenType::ENUM_MEMBER, SemanticTokenType::DECORATOR, SemanticTokenType::FUNCTION, SemanticTokenType::METHOD, SemanticTokenType::MACRO, SemanticTokenType::new("label"), // Not in the spec, but in the docs. SemanticTokenType::COMMENT, SemanticTokenType::STRING, SemanticTokenType::KEYWORD, SemanticTokenType::NUMBER, SemanticTokenType::REGEXP, SemanticTokenType::OPERATOR, SemanticTokenType::MODIFIER, // Only in the spec, not in the docs. // Language specific things below. // C# SemanticTokenType::EVENT, // Rust SemanticTokenType::new("lifetime"),];pub const SEMANTIC_TOKEN_MODIFIERS: &[SemanticTokenModifier] = &[ SemanticTokenModifier::DECLARATION, SemanticTokenModifier::DEFINITION, SemanticTokenModifier::READONLY, SemanticTokenModifier::STATIC, SemanticTokenModifier::DEPRECATED, SemanticTokenModifier::ABSTRACT, SemanticTokenModifier::ASYNC, SemanticTokenModifier::MODIFICATION, SemanticTokenModifier::DOCUMENTATION, SemanticTokenModifier::DEFAULT_LIBRARY, // Language specific things below. // Rust SemanticTokenModifier::new("constant"),]; impl LanguageServer { /// Starts a language server process. /// A request_timeout of zero or Duration::MAX indicates an indefinite timeout. pub fn new( stderr_capture: Arc<Mutex<Option<String>>>, server_id: LanguageServerId, server_name: LanguageServerName, binary: LanguageServerBinary, root_path: &Path, code_action_kinds: Option<Vec<CodeActionKind>>, workspace_folders: Option<Arc<Mutex<BTreeSet<Uri>>>>, cx: &mut AsyncApp, ) -> Result<Self> { let working_dir = if root_path.is_dir() { root_path } else { root_path.parent().unwrap_or_else(|| Path::new("/")) }; let root_uri = Uri::from_file_path(&working_dir) .map_err(|()| anyhow!("{working_dir:?} is not a valid URI"))?; log::info!( "starting language server process. binary path: \ {:?}, working directory: {:?}, args: {:?}", binary.path, working_dir, &binary.arguments ); let mut command = util::command::new_command(&binary.path); command .current_dir(working_dir) .args(&binary.arguments) .envs(binary.env.clone().unwrap_or_default()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(true); let mut server = command .spawn() .with_context(|| format!("failed to spawn command {command:?}",))?; let stdin = server.stdin.take().unwrap(); let stdout = server.stdout.take().unwrap(); let stderr = server.stderr.take().unwrap(); let server = Self::new_internal( server_id, server_name, stdin, stdout, Some(stderr), stderr_capture, Some(server), code_action_kinds, binary, root_uri, workspace_folders, cx, move |notification| { log::info!( "Language server with id {} sent unhandled notification {}:\n{}", server_id, notification.method, serde_json::to_string_pretty(¬ification.params).unwrap(), ); false }, ); Ok(server) } fn new_internal<Stdin, Stdout, Stderr, F>( server_id: LanguageServerId, server_name: LanguageServerName, stdin: Stdin, stdout: Stdout, stderr: Option<Stderr>, stderr_capture: Arc<Mutex<Option<String>>>, server: Option<Child>, code_action_kinds: Option<Vec<CodeActionKind>>, binary: LanguageServerBinary, root_uri: Uri, workspace_folders: Option<Arc<Mutex<BTreeSet<Uri>>>>, cx: &mut AsyncApp, on_unhandled_notification: F, ) -> Self where Stdin: AsyncWrite + Unpin + Send + 'static, Stdout: AsyncRead + Unpin + Send + 'static, Stderr: AsyncRead + Unpin + Send + 'static, F: Fn(&NotificationOrRequest) -> bool + 'static + Send + Sync + Clone,Lines 1–500 of 2339. Load more
mod input_handler; pub use lsp_types::request::*; pub use lsp_types::*; use anyhow::{Context as _, Result, anyhow}; use collections::{BTreeMap, HashMap}; use futures::{ AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, Future, FutureExt, StreamExt, channel::oneshot::{self, Canceled}, future::{self, Either}, io::{BufReader, BufWriter}, select, }; use gpui::{App, AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Task}; use notification::DidChangeWorkspaceFolders; use parking_lot::{Mutex, RwLock}; use postage::{barrier, prelude::Stream}; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::{Value, json, value::RawValue}; use util::command::{Child, Stdio}; use gpui_util::{ResultExt, TryFutureExt}; use std::path::Path; use std::{ any::TypeId, collections::BTreeSet, ffi::{OsStr, OsString}, fmt, io::Write, ops::DerefMut, path::PathBuf, pin::Pin, sync::{ Arc, Weak, atomic::{AtomicI32, Ordering::SeqCst}, }, task::Poll, time::{Duration, Instant}, }; use util::{ConnectionResult, redact}; const JSON_RPC_VERSION: &str = "2.0"; const CONTENT_LEN_HEADER: &str = "Content-Length: "; /// The default amount of time to wait while initializing or fetching LSP servers, in seconds. /// /// Should not be used (in favor of DEFAULT_LSP_REQUEST_TIMEOUT) and is exported solely for use inside ProjectSettings defaults. pub const DEFAULT_LSP_REQUEST_TIMEOUT_SECS: u64 = 120; /// A timeout representing the value of [DEFAULT_LSP_REQUEST_TIMEOUT_SECS]. /// /// Should **only be used** in tests and as a fallback when a corresponding config value cannot be obtained! pub const DEFAULT_LSP_REQUEST_TIMEOUT: Duration = Duration::from_secs(DEFAULT_LSP_REQUEST_TIMEOUT_SECS); /// The shutdown timeout for LSP servers (including Prettier/Copilot). const SERVER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); pub fn workspace_folder_for_uri(uri: Uri) -> WorkspaceFolder { let name = uri .to_file_path() .ok() .map(|path| { let name = path.file_name().unwrap_or(path.as_os_str()); name.to_string_lossy().into_owned() }) .filter(|name| !name.is_empty()) .or_else(|| { uri.path_segments() .and_then(|mut segments| segments.rfind(|segment| !segment.is_empty())) .map(str::to_owned) }) .unwrap_or_else(|| uri.as_str().to_owned()); WorkspaceFolder { uri, name } } type NotificationHandler = Box<dyn Send + FnMut(Option<RequestId>, Value, &mut AsyncApp)>; type PendingRespondTasks = Arc<Mutex<HashMap<RequestId, Task<()>>>>; type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>) -> Task<()>>; 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, StdIn, 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(Clone, Serialize)] pub struct LanguageServerBinary { pub path: PathBuf, pub arguments: Vec<OsString>, pub env: Option<HashMap<String, String>>, } /// Configures the search (and installation) of language servers. #[derive(Debug, Clone)] pub struct LanguageServerBinaryOptions { /// Whether the adapter should look at the users system pub allow_path_lookup: bool, /// Whether the adapter should download its own version pub allow_binary_download: bool, /// Whether the adapter should download a pre-release version pub pre_release: bool, } struct NotificationSerializer(Box<dyn FnOnce() -> String + Send + Sync>); /// A running language server process. pub struct LanguageServer { server_id: LanguageServerId, next_id: AtomicI32, outbound_tx: async_channel::Sender<String>, notification_tx: async_channel::Sender<NotificationSerializer>, name: LanguageServerName, version: Option<SharedString>, process_name: Arc<str>, binary: LanguageServerBinary, capabilities: RwLock<ServerCapabilities>, /// Configuration sent to the server, stored for display in the language server logs /// buffer. This is represented as the message sent to the LSP in order to avoid cloning it (can /// be large in cases like sending schemas to the json server). configuration: Arc<DidChangeConfigurationParams>, code_action_kinds: Option<Vec<CodeActionKind>>, notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>, response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>, /// Tasks spawned by `on_custom_request` to compute responses. Tracked so that /// incoming `$/cancelRequest` notifications can cancel them by dropping the task. pending_respond_tasks: PendingRespondTasks, io_handlers: Arc<Mutex<HashMap<i32, IoHandler>>>, executor: BackgroundExecutor, #[allow(clippy::type_complexity)] io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>, output_done_rx: Mutex<Option<barrier::Receiver>>, server: Arc<Mutex<Option<Child>>>, workspace_folders: Option<Arc<Mutex<BTreeSet<Uri>>>>, root_uri: Uri, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum LanguageServerSelector { Id(LanguageServerId), Name(LanguageServerName), } /// Identifies a running language server. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct LanguageServerId(pub usize); impl LanguageServerId { pub fn from_proto(id: u64) -> Self { Self(id as usize) } pub fn to_proto(self) -> u64 { self.0 as u64 } } /// A name of a language server. #[derive( Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, JsonSchema, )] #[serde(transparent)] pub struct LanguageServerName(pub SharedString); impl std::fmt::Display for LanguageServerName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, f) } } impl AsRef<str> for LanguageServerName { fn as_ref(&self) -> &str { self.0.as_ref() } } impl AsRef<OsStr> for LanguageServerName { fn as_ref(&self) -> &OsStr { self.0.as_ref().as_ref() } } impl LanguageServerName { pub const fn new_static(s: &'static str) -> Self { Self(SharedString::new_static(s)) } pub fn from_proto(s: String) -> Self { Self(s.into()) } } impl<'a> From<&'a str> for LanguageServerName { fn from(str: &'a str) -> LanguageServerName { LanguageServerName(str.to_string().into()) } } impl PartialEq<str> for LanguageServerName { fn eq(&self, other: &str) -> bool { self.0 == other } } /// Handle to a language server RPC activity subscription. pub enum Subscription { Notification { method: &'static str, notification_handlers: Option<Weak<Mutex<HashMap<&'static str, NotificationHandler>>>>, }, Io { id: i32, io_handlers: Option<Weak<Mutex<HashMap<i32, IoHandler>>>>, }, } /// Language server protocol RPC request message ID. /// /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage) #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(untagged)] pub enum RequestId { Int(i32), Str(String), } fn is_unit<T: 'static>(_: &T) -> bool { TypeId::of::<T>() == TypeId::of::<()>() } /// 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> where T: 'static, { jsonrpc: &'static str, id: RequestId, method: &'a str, #[serde(default, skip_serializing_if = "is_unit")] 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, id: RequestId, #[serde(default)] error: Option<Error>, #[serde(borrow)] 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, id: RequestId, #[serde(flatten)] value: LspResult<T>, } #[derive(Serialize)] #[serde(rename_all = "snake_case")] enum LspResult<T> { #[serde(rename = "result")] Ok(Option<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> where T: 'static, { jsonrpc: &'static str, #[serde(borrow)] method: &'a str, #[serde(default, skip_serializing_if = "is_unit")] params: T, } /// Language server RPC notification message before it is deserialized into a concrete type. #[derive(Debug, Clone, Deserialize)] struct NotificationOrRequest { #[serde(default)] id: Option<RequestId>, method: String, #[serde(default)] params: Option<Value>, } #[derive(Debug, Serialize, Deserialize)] struct Error { code: i64, message: String, #[serde(default)] data: Option<serde_json::Value>, } pub trait LspRequestFuture<O>: Future<Output = ConnectionResult<O>> { fn id(&self) -> i32; } struct LspRequest<F> { id: i32, request: F, } impl<F> LspRequest<F> { pub fn new(id: i32, request: F) -> Self { Self { id, request } } } impl<F: Future> Future for LspRequest<F> { type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> { // SAFETY: This is standard pin projection, we're pinned so our fields must be pinned. let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().request) }; inner.poll(cx) } } impl<F, O> LspRequestFuture<O> for LspRequest<F> where F: Future<Output = ConnectionResult<O>>, { fn id(&self) -> i32 { self.id } } /// Combined capabilities of the server and the adapter. #[derive(Debug, Clone)] pub struct AdapterServerCapabilities { // Reported capabilities by the server pub server_capabilities: ServerCapabilities, // List of code actions supported by the LspAdapter matching the server pub code_action_kinds: Option<Vec<CodeActionKind>>, } // See the VSCode docs [1] and the LSP Spec [2] // // [1]: https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide#standard-token-types-and-modifiers // [2]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokenTypes pub const SEMANTIC_TOKEN_TYPES: &[SemanticTokenType] = &[ SemanticTokenType::NAMESPACE, SemanticTokenType::CLASS, SemanticTokenType::ENUM, SemanticTokenType::INTERFACE, SemanticTokenType::STRUCT, SemanticTokenType::TYPE_PARAMETER, SemanticTokenType::TYPE, SemanticTokenType::PARAMETER, SemanticTokenType::VARIABLE, SemanticTokenType::PROPERTY, SemanticTokenType::ENUM_MEMBER, SemanticTokenType::DECORATOR, SemanticTokenType::FUNCTION, SemanticTokenType::METHOD, SemanticTokenType::MACRO, SemanticTokenType::new("label"), // Not in the spec, but in the docs. SemanticTokenType::COMMENT, SemanticTokenType::STRING, SemanticTokenType::KEYWORD, SemanticTokenType::NUMBER, SemanticTokenType::REGEXP, SemanticTokenType::OPERATOR, SemanticTokenType::MODIFIER, // Only in the spec, not in the docs. // Language specific things below. // C# SemanticTokenType::EVENT, // Rust SemanticTokenType::new("lifetime"), ]; pub const SEMANTIC_TOKEN_MODIFIERS: &[SemanticTokenModifier] = &[ SemanticTokenModifier::DECLARATION, SemanticTokenModifier::DEFINITION, SemanticTokenModifier::READONLY, SemanticTokenModifier::STATIC, SemanticTokenModifier::DEPRECATED, SemanticTokenModifier::ABSTRACT, SemanticTokenModifier::ASYNC, SemanticTokenModifier::MODIFICATION, SemanticTokenModifier::DOCUMENTATION, SemanticTokenModifier::DEFAULT_LIBRARY, // Language specific things below. // Rust SemanticTokenModifier::new("constant"), ]; impl LanguageServer { /// Starts a language server process. /// A request_timeout of zero or Duration::MAX indicates an indefinite timeout. pub fn new( stderr_capture: Arc<Mutex<Option<String>>>, server_id: LanguageServerId, server_name: LanguageServerName, binary: LanguageServerBinary, root_path: &Path, code_action_kinds: Option<Vec<CodeActionKind>>, workspace_folders: Option<Arc<Mutex<BTreeSet<Uri>>>>, cx: &mut AsyncApp, ) -> Result<Self> { let working_dir = if root_path.is_dir() { root_path } else { root_path.parent().unwrap_or_else(|| Path::new("/")) }; let root_uri = Uri::from_file_path(&working_dir) .map_err(|()| anyhow!("{working_dir:?} is not a valid URI"))?; log::info!( "starting language server process. binary path: \ {:?}, working directory: {:?}, args: {:?}", binary.path, working_dir, &binary.arguments ); let mut command = util::command::new_command(&binary.path); command .current_dir(working_dir) .args(&binary.arguments) .envs(binary.env.clone().unwrap_or_default()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(true); let mut server = command .spawn() .with_context(|| format!("failed to spawn command {command:?}",))?; let stdin = server.stdin.take().unwrap(); let stdout = server.stdout.take().unwrap(); let stderr = server.stderr.take().unwrap(); let server = Self::new_internal( server_id, server_name, stdin, stdout, Some(stderr), stderr_capture, Some(server), code_action_kinds, binary, root_uri, workspace_folders, cx, move |notification| { log::info!( "Language server with id {} sent unhandled notification {}:\n{}", server_id, notification.method, serde_json::to_string_pretty(¬ification.params).unwrap(), ); false }, ); Ok(server) } fn new_internal<Stdin, Stdout, Stderr, F>( server_id: LanguageServerId, server_name: LanguageServerName, stdin: Stdin, stdout: Stdout, stderr: Option<Stderr>, stderr_capture: Arc<Mutex<Option<String>>>, server: Option<Child>, code_action_kinds: Option<Vec<CodeActionKind>>, binary: LanguageServerBinary, root_uri: Uri, workspace_folders: Option<Arc<Mutex<BTreeSet<Uri>>>>, cx: &mut AsyncApp, on_unhandled_notification: F, ) -> Self where Stdin: AsyncWrite + Unpin + Send + 'static, Stdout: AsyncRead + Unpin + Send + 'static, Stderr: AsyncRead + Unpin + Send + 'static, F: Fn(&NotificationOrRequest) -> bool + 'static + Send + Sync + Clone,