diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /src/tools/rust-analyzer/lib | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/lib')
-rw-r--r-- | src/tools/rust-analyzer/lib/README.md | 2 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/la-arena/Cargo.toml | 10 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/la-arena/src/lib.rs | 366 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/la-arena/src/map.rs | 75 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/lsp-server/Cargo.toml | 16 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/lsp-server/examples/goto_def.rs | 121 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/lsp-server/src/error.rs | 50 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/lsp-server/src/lib.rs | 232 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/lsp-server/src/msg.rs | 343 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/lsp-server/src/req_queue.rs | 62 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/lsp-server/src/socket.rs | 46 | ||||
-rw-r--r-- | src/tools/rust-analyzer/lib/lsp-server/src/stdio.rs | 71 |
12 files changed, 1394 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/lib/README.md b/src/tools/rust-analyzer/lib/README.md new file mode 100644 index 000000000..6b2eeac2c --- /dev/null +++ b/src/tools/rust-analyzer/lib/README.md @@ -0,0 +1,2 @@ +Crates in this directory are published to crates.io and obey semver. +They *could* live in a separate repo, but we want to experiment with a monorepo setup. diff --git a/src/tools/rust-analyzer/lib/la-arena/Cargo.toml b/src/tools/rust-analyzer/lib/la-arena/Cargo.toml new file mode 100644 index 000000000..ec5ba8ba0 --- /dev/null +++ b/src/tools/rust-analyzer/lib/la-arena/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "la-arena" +version = "0.3.0" +description = "Simple index-based arena without deletion." +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/la-arena" +documentation = "https://docs.rs/la-arena" +categories = ["data-structures", "memory-management", "rust-patterns"] +edition = "2021" +rust-version = "1.56" diff --git a/src/tools/rust-analyzer/lib/la-arena/src/lib.rs b/src/tools/rust-analyzer/lib/la-arena/src/lib.rs new file mode 100644 index 000000000..dadee43b1 --- /dev/null +++ b/src/tools/rust-analyzer/lib/la-arena/src/lib.rs @@ -0,0 +1,366 @@ +//! Yet another index-based arena. + +#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] +#![warn(missing_docs)] + +use std::{ + fmt, + hash::{Hash, Hasher}, + iter::FromIterator, + marker::PhantomData, + ops::{Index, IndexMut, Range, RangeInclusive}, +}; + +mod map; +pub use map::ArenaMap; + +/// The raw index of a value in an arena. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RawIdx(u32); + +impl From<RawIdx> for u32 { + fn from(raw: RawIdx) -> u32 { + raw.0 + } +} + +impl From<u32> for RawIdx { + fn from(idx: u32) -> RawIdx { + RawIdx(idx) + } +} + +impl fmt::Debug for RawIdx { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for RawIdx { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +/// The index of a value allocated in an arena that holds `T`s. +pub struct Idx<T> { + raw: RawIdx, + _ty: PhantomData<fn() -> T>, +} + +impl<T> Clone for Idx<T> { + fn clone(&self) -> Self { + *self + } +} +impl<T> Copy for Idx<T> {} + +impl<T> PartialEq for Idx<T> { + fn eq(&self, other: &Idx<T>) -> bool { + self.raw == other.raw + } +} +impl<T> Eq for Idx<T> {} + +impl<T> Hash for Idx<T> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.raw.hash(state); + } +} + +impl<T> fmt::Debug for Idx<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut type_name = std::any::type_name::<T>(); + if let Some(idx) = type_name.rfind(':') { + type_name = &type_name[idx + 1..]; + } + write!(f, "Idx::<{}>({})", type_name, self.raw) + } +} + +impl<T> Idx<T> { + /// Creates a new index from a [`RawIdx`]. + pub fn from_raw(raw: RawIdx) -> Self { + Idx { raw, _ty: PhantomData } + } + + /// Converts this index into the underlying [`RawIdx`]. + pub fn into_raw(self) -> RawIdx { + self.raw + } +} + +/// A range of densely allocated arena values. +pub struct IdxRange<T> { + range: Range<u32>, + _p: PhantomData<T>, +} + +impl<T> IdxRange<T> { + /// Creates a new index range + /// inclusive of the start value and exclusive of the end value. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// let a = arena.alloc("a"); + /// let b = arena.alloc("b"); + /// let c = arena.alloc("c"); + /// let d = arena.alloc("d"); + /// + /// let range = la_arena::IdxRange::new(b..d); + /// assert_eq!(&arena[range], &["b", "c"]); + /// ``` + pub fn new(range: Range<Idx<T>>) -> Self { + Self { range: range.start.into_raw().into()..range.end.into_raw().into(), _p: PhantomData } + } + + /// Creates a new index range + /// inclusive of the start value and end value. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// let foo = arena.alloc("foo"); + /// let bar = arena.alloc("bar"); + /// let baz = arena.alloc("baz"); + /// + /// let range = la_arena::IdxRange::new_inclusive(foo..=baz); + /// assert_eq!(&arena[range], &["foo", "bar", "baz"]); + /// + /// let range = la_arena::IdxRange::new_inclusive(foo..=foo); + /// assert_eq!(&arena[range], &["foo"]); + /// ``` + pub fn new_inclusive(range: RangeInclusive<Idx<T>>) -> Self { + Self { + range: u32::from(range.start().into_raw())..u32::from(range.end().into_raw()) + 1, + _p: PhantomData, + } + } + + /// Returns whether the index range is empty. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// let one = arena.alloc(1); + /// let two = arena.alloc(2); + /// + /// assert!(la_arena::IdxRange::new(one..one).is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.range.is_empty() + } +} + +impl<T> Iterator for IdxRange<T> { + type Item = Idx<T>; + fn next(&mut self) -> Option<Self::Item> { + self.range.next().map(|raw| Idx::from_raw(raw.into())) + } +} + +impl<T> DoubleEndedIterator for IdxRange<T> { + fn next_back(&mut self) -> Option<Self::Item> { + self.range.next_back().map(|raw| Idx::from_raw(raw.into())) + } +} + +impl<T> fmt::Debug for IdxRange<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple(&format!("IdxRange::<{}>", std::any::type_name::<T>())) + .field(&self.range) + .finish() + } +} + +impl<T> Clone for IdxRange<T> { + fn clone(&self) -> Self { + Self { range: self.range.clone(), _p: PhantomData } + } +} + +impl<T> PartialEq for IdxRange<T> { + fn eq(&self, other: &Self) -> bool { + self.range == other.range + } +} + +impl<T> Eq for IdxRange<T> {} + +/// Yet another index-based arena. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Arena<T> { + data: Vec<T>, +} + +impl<T: fmt::Debug> fmt::Debug for Arena<T> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("Arena").field("len", &self.len()).field("data", &self.data).finish() + } +} + +impl<T> Arena<T> { + /// Creates a new empty arena. + /// + /// ``` + /// let arena: la_arena::Arena<i32> = la_arena::Arena::new(); + /// assert!(arena.is_empty()); + /// ``` + pub const fn new() -> Arena<T> { + Arena { data: Vec::new() } + } + + /// Empties the arena, removing all contained values. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// + /// arena.alloc(1); + /// arena.alloc(2); + /// arena.alloc(3); + /// assert_eq!(arena.len(), 3); + /// + /// arena.clear(); + /// assert!(arena.is_empty()); + /// ``` + pub fn clear(&mut self) { + self.data.clear(); + } + + /// Returns the length of the arena. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// assert_eq!(arena.len(), 0); + /// + /// arena.alloc("foo"); + /// assert_eq!(arena.len(), 1); + /// + /// arena.alloc("bar"); + /// assert_eq!(arena.len(), 2); + /// + /// arena.alloc("baz"); + /// assert_eq!(arena.len(), 3); + /// ``` + pub fn len(&self) -> usize { + self.data.len() + } + + /// Returns whether the arena contains no elements. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// assert!(arena.is_empty()); + /// + /// arena.alloc(0.5); + /// assert!(!arena.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Allocates a new value on the arena, returning the value’s index. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// let idx = arena.alloc(50); + /// + /// assert_eq!(arena[idx], 50); + /// ``` + pub fn alloc(&mut self, value: T) -> Idx<T> { + let idx = self.next_idx(); + self.data.push(value); + idx + } + + /// Returns an iterator over the arena’s elements. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// let idx1 = arena.alloc(20); + /// let idx2 = arena.alloc(40); + /// let idx3 = arena.alloc(60); + /// + /// let mut iterator = arena.iter(); + /// assert_eq!(iterator.next(), Some((idx1, &20))); + /// assert_eq!(iterator.next(), Some((idx2, &40))); + /// assert_eq!(iterator.next(), Some((idx3, &60))); + /// ``` + pub fn iter( + &self, + ) -> impl Iterator<Item = (Idx<T>, &T)> + ExactSizeIterator + DoubleEndedIterator { + self.data.iter().enumerate().map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value)) + } + + /// Returns an iterator over the arena’s mutable elements. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// let idx1 = arena.alloc(20); + /// + /// assert_eq!(arena[idx1], 20); + /// + /// let mut iterator = arena.iter_mut(); + /// *iterator.next().unwrap().1 = 10; + /// drop(iterator); + /// + /// assert_eq!(arena[idx1], 10); + /// ``` + pub fn iter_mut( + &mut self, + ) -> impl Iterator<Item = (Idx<T>, &mut T)> + ExactSizeIterator + DoubleEndedIterator { + self.data + .iter_mut() + .enumerate() + .map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value)) + } + + /// Reallocates the arena to make it take up as little space as possible. + pub fn shrink_to_fit(&mut self) { + self.data.shrink_to_fit(); + } + + /// Returns the index of the next value allocated on the arena. + /// + /// This method should remain private to make creating invalid `Idx`s harder. + fn next_idx(&self) -> Idx<T> { + Idx::from_raw(RawIdx(self.data.len() as u32)) + } +} + +impl<T> Default for Arena<T> { + fn default() -> Arena<T> { + Arena { data: Vec::new() } + } +} + +impl<T> Index<Idx<T>> for Arena<T> { + type Output = T; + fn index(&self, idx: Idx<T>) -> &T { + let idx = idx.into_raw().0 as usize; + &self.data[idx] + } +} + +impl<T> IndexMut<Idx<T>> for Arena<T> { + fn index_mut(&mut self, idx: Idx<T>) -> &mut T { + let idx = idx.into_raw().0 as usize; + &mut self.data[idx] + } +} + +impl<T> Index<IdxRange<T>> for Arena<T> { + type Output = [T]; + fn index(&self, range: IdxRange<T>) -> &[T] { + let start = range.range.start as usize; + let end = range.range.end as usize; + &self.data[start..end] + } +} + +impl<T> FromIterator<T> for Arena<T> { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = T>, + { + Arena { data: Vec::from_iter(iter) } + } +} diff --git a/src/tools/rust-analyzer/lib/la-arena/src/map.rs b/src/tools/rust-analyzer/lib/la-arena/src/map.rs new file mode 100644 index 000000000..d27f086d3 --- /dev/null +++ b/src/tools/rust-analyzer/lib/la-arena/src/map.rs @@ -0,0 +1,75 @@ +use std::marker::PhantomData; + +use crate::Idx; + +/// A map from arena indexes to some other type. +/// Space requirement is O(highest index). +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ArenaMap<IDX, V> { + v: Vec<Option<V>>, + _ty: PhantomData<IDX>, +} + +impl<T, V> ArenaMap<Idx<T>, V> { + /// Inserts a value associated with a given arena index into the map. + pub fn insert(&mut self, idx: Idx<T>, t: V) { + let idx = Self::to_idx(idx); + + self.v.resize_with((idx + 1).max(self.v.len()), || None); + self.v[idx] = Some(t); + } + + /// Returns a reference to the value associated with the provided index + /// if it is present. + pub fn get(&self, idx: Idx<T>) -> Option<&V> { + self.v.get(Self::to_idx(idx)).and_then(|it| it.as_ref()) + } + + /// Returns a mutable reference to the value associated with the provided index + /// if it is present. + pub fn get_mut(&mut self, idx: Idx<T>) -> Option<&mut V> { + self.v.get_mut(Self::to_idx(idx)).and_then(|it| it.as_mut()) + } + + /// Returns an iterator over the values in the map. + pub fn values(&self) -> impl Iterator<Item = &V> { + self.v.iter().filter_map(|o| o.as_ref()) + } + + /// Returns an iterator over mutable references to the values in the map. + pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> { + self.v.iter_mut().filter_map(|o| o.as_mut()) + } + + /// Returns an iterator over the arena indexes and values in the map. + pub fn iter(&self) -> impl Iterator<Item = (Idx<T>, &V)> { + self.v.iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_ref()?))) + } + + fn to_idx(idx: Idx<T>) -> usize { + u32::from(idx.into_raw()) as usize + } + + fn from_idx(idx: usize) -> Idx<T> { + Idx::from_raw((idx as u32).into()) + } +} + +impl<T, V> std::ops::Index<Idx<V>> for ArenaMap<Idx<V>, T> { + type Output = T; + fn index(&self, idx: Idx<V>) -> &T { + self.v[Self::to_idx(idx)].as_ref().unwrap() + } +} + +impl<T, V> std::ops::IndexMut<Idx<V>> for ArenaMap<Idx<V>, T> { + fn index_mut(&mut self, idx: Idx<V>) -> &mut T { + self.v[Self::to_idx(idx)].as_mut().unwrap() + } +} + +impl<T, V> Default for ArenaMap<Idx<V>, T> { + fn default() -> Self { + ArenaMap { v: Vec::new(), _ty: PhantomData } + } +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml new file mode 100644 index 000000000..204d120d0 --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "lsp-server" +version = "0.6.0" +description = "Generic LSP server scaffold." +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/lsp-server" +edition = "2021" + +[dependencies] +log = "0.4.17" +serde_json = "1.0.81" +serde = { version = "1.0.137", features = ["derive"] } +crossbeam-channel = "0.5.5" + +[dev-dependencies] +lsp-types = "0.93.0" diff --git a/src/tools/rust-analyzer/lib/lsp-server/examples/goto_def.rs b/src/tools/rust-analyzer/lib/lsp-server/examples/goto_def.rs new file mode 100644 index 000000000..ca7ad0b53 --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/examples/goto_def.rs @@ -0,0 +1,121 @@ +//! A minimal example LSP server that can only respond to the `gotoDefinition` request. To use +//! this example, execute it and then send an `initialize` request. +//! +//! ```no_run +//! Content-Length: 85 +//! +//! {"jsonrpc": "2.0", "method": "initialize", "id": 1, "params": {"capabilities": {}}} +//! ``` +//! +//! This will respond with a server response. Then send it a `initialized` notification which will +//! have no response. +//! +//! ```no_run +//! Content-Length: 59 +//! +//! {"jsonrpc": "2.0", "method": "initialized", "params": {}} +//! ``` +//! +//! Once these two are sent, then we enter the main loop of the server. The only request this +//! example can handle is `gotoDefinition`: +//! +//! ```no_run +//! Content-Length: 159 +//! +//! {"jsonrpc": "2.0", "method": "textDocument/definition", "id": 2, "params": {"textDocument": {"uri": "file://temp"}, "position": {"line": 1, "character": 1}}} +//! ``` +//! +//! To finish up without errors, send a shutdown request: +//! +//! ```no_run +//! Content-Length: 67 +//! +//! {"jsonrpc": "2.0", "method": "shutdown", "id": 3, "params": null} +//! ``` +//! +//! The server will exit the main loop and finally we send a `shutdown` notification to stop +//! the server. +//! +//! ``` +//! Content-Length: 54 +//! +//! {"jsonrpc": "2.0", "method": "exit", "params": null} +//! ``` +use std::error::Error; + +use lsp_types::OneOf; +use lsp_types::{ + request::GotoDefinition, GotoDefinitionResponse, InitializeParams, ServerCapabilities, +}; + +use lsp_server::{Connection, ExtractError, Message, Request, RequestId, Response}; + +fn main() -> Result<(), Box<dyn Error + Sync + Send>> { + // Note that we must have our logging only write out to stderr. + eprintln!("starting generic LSP server"); + + // Create the transport. Includes the stdio (stdin and stdout) versions but this could + // also be implemented to use sockets or HTTP. + let (connection, io_threads) = Connection::stdio(); + + // Run the server and wait for the two threads to end (typically by trigger LSP Exit event). + let server_capabilities = serde_json::to_value(&ServerCapabilities { + definition_provider: Some(OneOf::Left(true)), + ..Default::default() + }) + .unwrap(); + let initialization_params = connection.initialize(server_capabilities)?; + main_loop(connection, initialization_params)?; + io_threads.join()?; + + // Shut down gracefully. + eprintln!("shutting down server"); + Ok(()) +} + +fn main_loop( + connection: Connection, + params: serde_json::Value, +) -> Result<(), Box<dyn Error + Sync + Send>> { + let _params: InitializeParams = serde_json::from_value(params).unwrap(); + eprintln!("starting example main loop"); + for msg in &connection.receiver { + eprintln!("got msg: {:?}", msg); + match msg { + Message::Request(req) => { + if connection.handle_shutdown(&req)? { + return Ok(()); + } + eprintln!("got request: {:?}", req); + match cast::<GotoDefinition>(req) { + Ok((id, params)) => { + eprintln!("got gotoDefinition request #{}: {:?}", id, params); + let result = Some(GotoDefinitionResponse::Array(Vec::new())); + let result = serde_json::to_value(&result).unwrap(); + let resp = Response { id, result: Some(result), error: None }; + connection.sender.send(Message::Response(resp))?; + continue; + } + Err(err @ ExtractError::JsonError { .. }) => panic!("{:?}", err), + Err(ExtractError::MethodMismatch(req)) => req, + }; + // ... + } + Message::Response(resp) => { + eprintln!("got response: {:?}", resp); + } + Message::Notification(not) => { + eprintln!("got notification: {:?}", not); + } + } + } + Ok(()) +} + +fn cast<R>(req: Request) -> Result<(RequestId, R::Params), ExtractError<Request>> +where + R: lsp_types::request::Request, + R::Params: serde::de::DeserializeOwned, +{ + req.extract(R::METHOD) +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/error.rs b/src/tools/rust-analyzer/lib/lsp-server/src/error.rs new file mode 100644 index 000000000..4c934d9ec --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/src/error.rs @@ -0,0 +1,50 @@ +use std::fmt; + +use crate::{Notification, Request}; + +#[derive(Debug, Clone)] +pub struct ProtocolError(pub(crate) String); + +impl std::error::Error for ProtocolError {} + +impl fmt::Display for ProtocolError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +#[derive(Debug)] +pub enum ExtractError<T> { + /// The extracted message was of a different method than expected. + MethodMismatch(T), + /// Failed to deserialize the message. + JsonError { method: String, error: serde_json::Error }, +} + +impl std::error::Error for ExtractError<Request> {} +impl fmt::Display for ExtractError<Request> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ExtractError::MethodMismatch(req) => { + write!(f, "Method mismatch for request '{}'", req.method) + } + ExtractError::JsonError { method, error } => { + write!(f, "Invalid request\nMethod: {method}\n error: {error}",) + } + } + } +} + +impl std::error::Error for ExtractError<Notification> {} +impl fmt::Display for ExtractError<Notification> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ExtractError::MethodMismatch(req) => { + write!(f, "Method mismatch for notification '{}'", req.method) + } + ExtractError::JsonError { method, error } => { + write!(f, "Invalid notification\nMethod: {method}\n error: {error}") + } + } + } +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs b/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs new file mode 100644 index 000000000..d567077d4 --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs @@ -0,0 +1,232 @@ +//! A language server scaffold, exposing a synchronous crossbeam-channel based API. +//! This crate handles protocol handshaking and parsing messages, while you +//! control the message dispatch loop yourself. +//! +//! Run with `RUST_LOG=lsp_server=debug` to see all the messages. + +#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] + +mod msg; +mod stdio; +mod error; +mod socket; +mod req_queue; + +use std::{ + io, + net::{TcpListener, TcpStream, ToSocketAddrs}, +}; + +use crossbeam_channel::{Receiver, Sender}; + +pub use crate::{ + error::{ExtractError, ProtocolError}, + msg::{ErrorCode, Message, Notification, Request, RequestId, Response, ResponseError}, + req_queue::{Incoming, Outgoing, ReqQueue}, + stdio::IoThreads, +}; + +/// Connection is just a pair of channels of LSP messages. +pub struct Connection { + pub sender: Sender<Message>, + pub receiver: Receiver<Message>, +} + +impl Connection { + /// Create connection over standard in/standard out. + /// + /// Use this to create a real language server. + pub fn stdio() -> (Connection, IoThreads) { + let (sender, receiver, io_threads) = stdio::stdio_transport(); + (Connection { sender, receiver }, io_threads) + } + + /// Open a connection over tcp. + /// This call blocks until a connection is established. + /// + /// Use this to create a real language server. + pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<(Connection, IoThreads)> { + let stream = TcpStream::connect(addr)?; + let (sender, receiver, io_threads) = socket::socket_transport(stream); + Ok((Connection { sender, receiver }, io_threads)) + } + + /// Listen for a connection over tcp. + /// This call blocks until a connection is established. + /// + /// Use this to create a real language server. + pub fn listen<A: ToSocketAddrs>(addr: A) -> io::Result<(Connection, IoThreads)> { + let listener = TcpListener::bind(addr)?; + let (stream, _) = listener.accept()?; + let (sender, receiver, io_threads) = socket::socket_transport(stream); + Ok((Connection { sender, receiver }, io_threads)) + } + + /// Creates a pair of connected connections. + /// + /// Use this for testing. + pub fn memory() -> (Connection, Connection) { + let (s1, r1) = crossbeam_channel::unbounded(); + let (s2, r2) = crossbeam_channel::unbounded(); + (Connection { sender: s1, receiver: r2 }, Connection { sender: s2, receiver: r1 }) + } + + /// Starts the initialization process by waiting for an initialize + /// request from the client. Use this for more advanced customization than + /// `initialize` can provide. + /// + /// Returns the request id and serialized `InitializeParams` from the client. + /// + /// # Example + /// + /// ```no_run + /// use std::error::Error; + /// use lsp_types::{ClientCapabilities, InitializeParams, ServerCapabilities}; + /// + /// use lsp_server::{Connection, Message, Request, RequestId, Response}; + /// + /// fn main() -> Result<(), Box<dyn Error + Sync + Send>> { + /// // Create the transport. Includes the stdio (stdin and stdout) versions but this could + /// // also be implemented to use sockets or HTTP. + /// let (connection, io_threads) = Connection::stdio(); + /// + /// // Run the server + /// let (id, params) = connection.initialize_start()?; + /// + /// let init_params: InitializeParams = serde_json::from_value(params).unwrap(); + /// let client_capabilities: ClientCapabilities = init_params.capabilities; + /// let server_capabilities = ServerCapabilities::default(); + /// + /// let initialize_data = serde_json::json!({ + /// "capabilities": server_capabilities, + /// "serverInfo": { + /// "name": "lsp-server-test", + /// "version": "0.1" + /// } + /// }); + /// + /// connection.initialize_finish(id, initialize_data)?; + /// + /// // ... Run main loop ... + /// + /// Ok(()) + /// } + /// ``` + pub fn initialize_start(&self) -> Result<(RequestId, serde_json::Value), ProtocolError> { + loop { + match self.receiver.recv() { + Ok(Message::Request(req)) if req.is_initialize() => { + return Ok((req.id, req.params)) + } + // Respond to non-initialize requests with ServerNotInitialized + Ok(Message::Request(req)) => { + let resp = Response::new_err( + req.id.clone(), + ErrorCode::ServerNotInitialized as i32, + format!("expected initialize request, got {:?}", req), + ); + self.sender.send(resp.into()).unwrap(); + } + Ok(msg) => { + return Err(ProtocolError(format!( + "expected initialize request, got {:?}", + msg + ))) + } + Err(e) => { + return Err(ProtocolError(format!( + "expected initialize request, got error: {}", + e + ))) + } + }; + } + } + + /// Finishes the initialization process by sending an `InitializeResult` to the client + pub fn initialize_finish( + &self, + initialize_id: RequestId, + initialize_result: serde_json::Value, + ) -> Result<(), ProtocolError> { + let resp = Response::new_ok(initialize_id, initialize_result); + self.sender.send(resp.into()).unwrap(); + match &self.receiver.recv() { + Ok(Message::Notification(n)) if n.is_initialized() => (), + Ok(msg) => { + return Err(ProtocolError(format!( + "expected Message::Notification, got: {:?}", + msg, + ))) + } + Err(e) => { + return Err(ProtocolError(format!( + "expected initialized notification, got error: {}", + e, + ))) + } + } + Ok(()) + } + + /// Initialize the connection. Sends the server capabilities + /// to the client and returns the serialized client capabilities + /// on success. If more fine-grained initialization is required use + /// `initialize_start`/`initialize_finish`. + /// + /// # Example + /// + /// ```no_run + /// use std::error::Error; + /// use lsp_types::ServerCapabilities; + /// + /// use lsp_server::{Connection, Message, Request, RequestId, Response}; + /// + /// fn main() -> Result<(), Box<dyn Error + Sync + Send>> { + /// // Create the transport. Includes the stdio (stdin and stdout) versions but this could + /// // also be implemented to use sockets or HTTP. + /// let (connection, io_threads) = Connection::stdio(); + /// + /// // Run the server + /// let server_capabilities = serde_json::to_value(&ServerCapabilities::default()).unwrap(); + /// let initialization_params = connection.initialize(server_capabilities)?; + /// + /// // ... Run main loop ... + /// + /// Ok(()) + /// } + /// ``` + pub fn initialize( + &self, + server_capabilities: serde_json::Value, + ) -> Result<serde_json::Value, ProtocolError> { + let (id, params) = self.initialize_start()?; + + let initialize_data = serde_json::json!({ + "capabilities": server_capabilities, + }); + + self.initialize_finish(id, initialize_data)?; + + Ok(params) + } + + /// If `req` is `Shutdown`, respond to it and return `true`, otherwise return `false` + pub fn handle_shutdown(&self, req: &Request) -> Result<bool, ProtocolError> { + if !req.is_shutdown() { + return Ok(false); + } + let resp = Response::new_ok(req.id.clone(), ()); + let _ = self.sender.send(resp.into()); + match &self.receiver.recv_timeout(std::time::Duration::from_secs(30)) { + Ok(Message::Notification(n)) if n.is_exit() => (), + Ok(msg) => { + return Err(ProtocolError(format!("unexpected message during shutdown: {:?}", msg))) + } + Err(e) => { + return Err(ProtocolError(format!("unexpected error during shutdown: {}", e))) + } + } + Ok(true) + } +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/msg.rs b/src/tools/rust-analyzer/lib/lsp-server/src/msg.rs new file mode 100644 index 000000000..97e5bd35c --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/src/msg.rs @@ -0,0 +1,343 @@ +use std::{ + fmt, + io::{self, BufRead, Write}, +}; + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use crate::error::ExtractError; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum Message { + Request(Request), + Response(Response), + Notification(Notification), +} + +impl From<Request> for Message { + fn from(request: Request) -> Message { + Message::Request(request) + } +} + +impl From<Response> for Message { + fn from(response: Response) -> Message { + Message::Response(response) + } +} + +impl From<Notification> for Message { + fn from(notification: Notification) -> Message { + Message::Notification(notification) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(transparent)] +pub struct RequestId(IdRepr); + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(untagged)] +enum IdRepr { + I32(i32), + String(String), +} + +impl From<i32> for RequestId { + fn from(id: i32) -> RequestId { + RequestId(IdRepr::I32(id)) + } +} + +impl From<String> for RequestId { + fn from(id: String) -> RequestId { + RequestId(IdRepr::String(id)) + } +} + +impl fmt::Display for RequestId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + IdRepr::I32(it) => fmt::Display::fmt(it, f), + // Use debug here, to make it clear that `92` and `"92"` are + // different, and to reduce WTF factor if the sever uses `" "` as an + // ID. + IdRepr::String(it) => fmt::Debug::fmt(it, f), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Request { + pub id: RequestId, + pub method: String, + #[serde(default = "serde_json::Value::default")] + #[serde(skip_serializing_if = "serde_json::Value::is_null")] + pub params: serde_json::Value, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Response { + // JSON RPC allows this to be null if it was impossible + // to decode the request's id. Ignore this special case + // and just die horribly. + pub id: RequestId, + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option<serde_json::Value>, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option<ResponseError>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ResponseError { + pub code: i32, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option<serde_json::Value>, +} + +#[derive(Clone, Copy, Debug)] +#[allow(unused)] +pub enum ErrorCode { + // Defined by JSON RPC: + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + ServerErrorStart = -32099, + ServerErrorEnd = -32000, + + /// Error code indicating that a server received a notification or + /// request before the server has received the `initialize` request. + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + + // Defined by the protocol: + /// The client has canceled a request and a server has detected + /// the cancel. + RequestCanceled = -32800, + + /// The server detected that the content of a document got + /// modified outside normal conditions. A server should + /// NOT send this error code if it detects a content change + /// in it unprocessed messages. The result even computed + /// on an older state might still be useful for the client. + /// + /// If a client decides that a result is not of any use anymore + /// the client should cancel the request. + ContentModified = -32801, + + /// The server cancelled the request. This error code should + /// only be used for requests that explicitly support being + /// server cancellable. + /// + /// @since 3.17.0 + ServerCancelled = -32802, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Notification { + pub method: String, + #[serde(default = "serde_json::Value::default")] + #[serde(skip_serializing_if = "serde_json::Value::is_null")] + pub params: serde_json::Value, +} + +impl Message { + pub fn read(r: &mut impl BufRead) -> io::Result<Option<Message>> { + Message::_read(r) + } + fn _read(r: &mut dyn BufRead) -> io::Result<Option<Message>> { + let text = match read_msg_text(r)? { + None => return Ok(None), + Some(text) => text, + }; + let msg = serde_json::from_str(&text)?; + Ok(Some(msg)) + } + pub fn write(self, w: &mut impl Write) -> io::Result<()> { + self._write(w) + } + fn _write(self, w: &mut dyn Write) -> io::Result<()> { + #[derive(Serialize)] + struct JsonRpc { + jsonrpc: &'static str, + #[serde(flatten)] + msg: Message, + } + let text = serde_json::to_string(&JsonRpc { jsonrpc: "2.0", msg: self })?; + write_msg_text(w, &text) + } +} + +impl Response { + pub fn new_ok<R: Serialize>(id: RequestId, result: R) -> Response { + Response { id, result: Some(serde_json::to_value(result).unwrap()), error: None } + } + pub fn new_err(id: RequestId, code: i32, message: String) -> Response { + let error = ResponseError { code, message, data: None }; + Response { id, result: None, error: Some(error) } + } +} + +impl Request { + pub fn new<P: Serialize>(id: RequestId, method: String, params: P) -> Request { + Request { id, method, params: serde_json::to_value(params).unwrap() } + } + pub fn extract<P: DeserializeOwned>( + self, + method: &str, + ) -> Result<(RequestId, P), ExtractError<Request>> { + if self.method != method { + return Err(ExtractError::MethodMismatch(self)); + } + match serde_json::from_value(self.params) { + Ok(params) => Ok((self.id, params)), + Err(error) => Err(ExtractError::JsonError { method: self.method, error }), + } + } + + pub(crate) fn is_shutdown(&self) -> bool { + self.method == "shutdown" + } + pub(crate) fn is_initialize(&self) -> bool { + self.method == "initialize" + } +} + +impl Notification { + pub fn new(method: String, params: impl Serialize) -> Notification { + Notification { method, params: serde_json::to_value(params).unwrap() } + } + pub fn extract<P: DeserializeOwned>( + self, + method: &str, + ) -> Result<P, ExtractError<Notification>> { + if self.method != method { + return Err(ExtractError::MethodMismatch(self)); + } + match serde_json::from_value(self.params) { + Ok(params) => Ok(params), + Err(error) => Err(ExtractError::JsonError { method: self.method, error }), + } + } + pub(crate) fn is_exit(&self) -> bool { + self.method == "exit" + } + pub(crate) fn is_initialized(&self) -> bool { + self.method == "initialized" + } +} + +fn read_msg_text(inp: &mut dyn BufRead) -> io::Result<Option<String>> { + fn invalid_data(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, error) + } + macro_rules! invalid_data { + ($($tt:tt)*) => (invalid_data(format!($($tt)*))) + } + + let mut size = None; + let mut buf = String::new(); + loop { + buf.clear(); + if inp.read_line(&mut buf)? == 0 { + return Ok(None); + } + if !buf.ends_with("\r\n") { + return Err(invalid_data!("malformed header: {:?}", buf)); + } + let buf = &buf[..buf.len() - 2]; + if buf.is_empty() { + break; + } + let mut parts = buf.splitn(2, ": "); + let header_name = parts.next().unwrap(); + let header_value = + parts.next().ok_or_else(|| invalid_data!("malformed header: {:?}", buf))?; + if header_name == "Content-Length" { + size = Some(header_value.parse::<usize>().map_err(invalid_data)?); + } + } + let size: usize = size.ok_or_else(|| invalid_data!("no Content-Length"))?; + let mut buf = buf.into_bytes(); + buf.resize(size, 0); + inp.read_exact(&mut buf)?; + let buf = String::from_utf8(buf).map_err(invalid_data)?; + log::debug!("< {}", buf); + Ok(Some(buf)) +} + +fn write_msg_text(out: &mut dyn Write, msg: &str) -> io::Result<()> { + log::debug!("> {}", msg); + write!(out, "Content-Length: {}\r\n\r\n", msg.len())?; + out.write_all(msg.as_bytes())?; + out.flush()?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{Message, Notification, Request, RequestId}; + + #[test] + fn shutdown_with_explicit_null() { + let text = "{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"shutdown\", \"params\": null }"; + let msg: Message = serde_json::from_str(text).unwrap(); + + assert!( + matches!(msg, Message::Request(req) if req.id == 3.into() && req.method == "shutdown") + ); + } + + #[test] + fn shutdown_with_no_params() { + let text = "{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"shutdown\"}"; + let msg: Message = serde_json::from_str(text).unwrap(); + + assert!( + matches!(msg, Message::Request(req) if req.id == 3.into() && req.method == "shutdown") + ); + } + + #[test] + fn notification_with_explicit_null() { + let text = "{\"jsonrpc\": \"2.0\",\"method\": \"exit\", \"params\": null }"; + let msg: Message = serde_json::from_str(text).unwrap(); + + assert!(matches!(msg, Message::Notification(not) if not.method == "exit")); + } + + #[test] + fn notification_with_no_params() { + let text = "{\"jsonrpc\": \"2.0\",\"method\": \"exit\"}"; + let msg: Message = serde_json::from_str(text).unwrap(); + + assert!(matches!(msg, Message::Notification(not) if not.method == "exit")); + } + + #[test] + fn serialize_request_with_null_params() { + let msg = Message::Request(Request { + id: RequestId::from(3), + method: "shutdown".into(), + params: serde_json::Value::Null, + }); + let serialized = serde_json::to_string(&msg).unwrap(); + + assert_eq!("{\"id\":3,\"method\":\"shutdown\"}", serialized); + } + + #[test] + fn serialize_notification_with_null_params() { + let msg = Message::Notification(Notification { + method: "exit".into(), + params: serde_json::Value::Null, + }); + let serialized = serde_json::to_string(&msg).unwrap(); + + assert_eq!("{\"method\":\"exit\"}", serialized); + } +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/req_queue.rs b/src/tools/rust-analyzer/lib/lsp-server/src/req_queue.rs new file mode 100644 index 000000000..1f3d44715 --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/src/req_queue.rs @@ -0,0 +1,62 @@ +use std::collections::HashMap; + +use serde::Serialize; + +use crate::{ErrorCode, Request, RequestId, Response, ResponseError}; + +/// Manages the set of pending requests, both incoming and outgoing. +#[derive(Debug)] +pub struct ReqQueue<I, O> { + pub incoming: Incoming<I>, + pub outgoing: Outgoing<O>, +} + +impl<I, O> Default for ReqQueue<I, O> { + fn default() -> ReqQueue<I, O> { + ReqQueue { + incoming: Incoming { pending: HashMap::default() }, + outgoing: Outgoing { next_id: 0, pending: HashMap::default() }, + } + } +} + +#[derive(Debug)] +pub struct Incoming<I> { + pending: HashMap<RequestId, I>, +} + +#[derive(Debug)] +pub struct Outgoing<O> { + next_id: i32, + pending: HashMap<RequestId, O>, +} + +impl<I> Incoming<I> { + pub fn register(&mut self, id: RequestId, data: I) { + self.pending.insert(id, data); + } + pub fn cancel(&mut self, id: RequestId) -> Option<Response> { + let _data = self.complete(id.clone())?; + let error = ResponseError { + code: ErrorCode::RequestCanceled as i32, + message: "canceled by client".to_string(), + data: None, + }; + Some(Response { id, result: None, error: Some(error) }) + } + pub fn complete(&mut self, id: RequestId) -> Option<I> { + self.pending.remove(&id) + } +} + +impl<O> Outgoing<O> { + pub fn register<P: Serialize>(&mut self, method: String, params: P, data: O) -> Request { + let id = RequestId::from(self.next_id); + self.pending.insert(id.clone(), data); + self.next_id += 1; + Request::new(id, method, params) + } + pub fn complete(&mut self, id: RequestId) -> Option<O> { + self.pending.remove(&id) + } +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/socket.rs b/src/tools/rust-analyzer/lib/lsp-server/src/socket.rs new file mode 100644 index 000000000..4a59c4c0f --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/src/socket.rs @@ -0,0 +1,46 @@ +use std::{ + io::{self, BufReader}, + net::TcpStream, + thread, +}; + +use crossbeam_channel::{bounded, Receiver, Sender}; + +use crate::{ + stdio::{make_io_threads, IoThreads}, + Message, +}; + +pub(crate) fn socket_transport( + stream: TcpStream, +) -> (Sender<Message>, Receiver<Message>, IoThreads) { + let (reader_receiver, reader) = make_reader(stream.try_clone().unwrap()); + let (writer_sender, writer) = make_write(stream.try_clone().unwrap()); + let io_threads = make_io_threads(reader, writer); + (writer_sender, reader_receiver, io_threads) +} + +fn make_reader(stream: TcpStream) -> (Receiver<Message>, thread::JoinHandle<io::Result<()>>) { + let (reader_sender, reader_receiver) = bounded::<Message>(0); + let reader = thread::spawn(move || { + let mut buf_read = BufReader::new(stream); + while let Some(msg) = Message::read(&mut buf_read).unwrap() { + let is_exit = matches!(&msg, Message::Notification(n) if n.is_exit()); + reader_sender.send(msg).unwrap(); + if is_exit { + break; + } + } + Ok(()) + }); + (reader_receiver, reader) +} + +fn make_write(mut stream: TcpStream) -> (Sender<Message>, thread::JoinHandle<io::Result<()>>) { + let (writer_sender, writer_receiver) = bounded::<Message>(0); + let writer = thread::spawn(move || { + writer_receiver.into_iter().try_for_each(|it| it.write(&mut stream)).unwrap(); + Ok(()) + }); + (writer_sender, writer) +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/stdio.rs b/src/tools/rust-analyzer/lib/lsp-server/src/stdio.rs new file mode 100644 index 000000000..cdee6432d --- /dev/null +++ b/src/tools/rust-analyzer/lib/lsp-server/src/stdio.rs @@ -0,0 +1,71 @@ +use std::{ + io::{self, stdin, stdout}, + thread, +}; + +use crossbeam_channel::{bounded, Receiver, Sender}; + +use crate::Message; + +/// Creates an LSP connection via stdio. +pub(crate) fn stdio_transport() -> (Sender<Message>, Receiver<Message>, IoThreads) { + let (writer_sender, writer_receiver) = bounded::<Message>(0); + let writer = thread::spawn(move || { + let stdout = stdout(); + let mut stdout = stdout.lock(); + writer_receiver.into_iter().try_for_each(|it| it.write(&mut stdout))?; + Ok(()) + }); + let (reader_sender, reader_receiver) = bounded::<Message>(0); + let reader = thread::spawn(move || { + let stdin = stdin(); + let mut stdin = stdin.lock(); + while let Some(msg) = Message::read(&mut stdin)? { + let is_exit = match &msg { + Message::Notification(n) => n.is_exit(), + _ => false, + }; + + reader_sender.send(msg).unwrap(); + + if is_exit { + break; + } + } + Ok(()) + }); + let threads = IoThreads { reader, writer }; + (writer_sender, reader_receiver, threads) +} + +// Creates an IoThreads +pub(crate) fn make_io_threads( + reader: thread::JoinHandle<io::Result<()>>, + writer: thread::JoinHandle<io::Result<()>>, +) -> IoThreads { + IoThreads { reader, writer } +} + +pub struct IoThreads { + reader: thread::JoinHandle<io::Result<()>>, + writer: thread::JoinHandle<io::Result<()>>, +} + +impl IoThreads { + pub fn join(self) -> io::Result<()> { + match self.reader.join() { + Ok(r) => r?, + Err(err) => { + println!("reader panicked!"); + std::panic::panic_any(err) + } + } + match self.writer.join() { + Ok(r) => r, + Err(err) => { + println!("writer panicked!"); + std::panic::panic_any(err); + } + } + } +} |