From dc0db358abe19481e475e10c32149b53370f1a1c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 05:57:31 +0200 Subject: Merging upstream version 1.72.1+dfsg1. Signed-off-by: Daniel Baumann --- src/tools/rust-analyzer/lib/README.md | 7 +- src/tools/rust-analyzer/lib/la-arena/src/lib.rs | 124 ++++++++++- src/tools/rust-analyzer/lib/la-arena/src/map.rs | 70 +++++- src/tools/rust-analyzer/lib/line-index/Cargo.toml | 11 + src/tools/rust-analyzer/lib/line-index/src/lib.rs | 237 +++++++++++++++++++++ .../rust-analyzer/lib/line-index/src/tests.rs | 11 + src/tools/rust-analyzer/lib/line-index/tests/it.rs | 62 ++++++ src/tools/rust-analyzer/lib/lsp-server/Cargo.toml | 4 +- .../rust-analyzer/lib/lsp-server/src/error.rs | 2 +- src/tools/rust-analyzer/lib/lsp-server/src/lib.rs | 70 ++++++ 10 files changed, 579 insertions(+), 19 deletions(-) create mode 100644 src/tools/rust-analyzer/lib/line-index/Cargo.toml create mode 100644 src/tools/rust-analyzer/lib/line-index/src/lib.rs create mode 100644 src/tools/rust-analyzer/lib/line-index/src/tests.rs create mode 100644 src/tools/rust-analyzer/lib/line-index/tests/it.rs (limited to 'src/tools/rust-analyzer/lib') diff --git a/src/tools/rust-analyzer/lib/README.md b/src/tools/rust-analyzer/lib/README.md index 6b2eeac2c..ed55e31d6 100644 --- a/src/tools/rust-analyzer/lib/README.md +++ b/src/tools/rust-analyzer/lib/README.md @@ -1,2 +1,5 @@ -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. +# lib + +Crates in this directory are published to [crates.io](https://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/src/lib.rs b/src/tools/rust-analyzer/lib/la-arena/src/lib.rs index ccaaf3991..f39c3a3e4 100644 --- a/src/tools/rust-analyzer/lib/la-arena/src/lib.rs +++ b/src/tools/rust-analyzer/lib/la-arena/src/lib.rs @@ -4,8 +4,9 @@ #![warn(missing_docs)] use std::{ - fmt, + cmp, fmt, hash::{Hash, Hasher}, + iter::{Enumerate, FusedIterator}, marker::PhantomData, ops::{Index, IndexMut, Range, RangeInclusive}, }; @@ -17,13 +18,27 @@ pub use map::{ArenaMap, Entry, OccupiedEntry, VacantEntry}; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RawIdx(u32); +impl RawIdx { + /// Constructs a [`RawIdx`] from a u32. + pub const fn from_u32(u32: u32) -> Self { + RawIdx(u32) + } + + /// Deconstructs a [`RawIdx`] into the underlying u32. + pub const fn into_u32(self) -> u32 { + self.0 + } +} + impl From for u32 { + #[inline] fn from(raw: RawIdx) -> u32 { raw.0 } } impl From for RawIdx { + #[inline] fn from(idx: u32) -> RawIdx { RawIdx(idx) } @@ -47,6 +62,18 @@ pub struct Idx { _ty: PhantomData T>, } +impl Ord for Idx { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.raw.cmp(&other.raw) + } +} + +impl PartialOrd for Idx { + fn partial_cmp(&self, other: &Self) -> Option { + self.raw.partial_cmp(&other.raw) + } +} + impl Clone for Idx { fn clone(&self) -> Self { *self @@ -79,12 +106,12 @@ impl fmt::Debug for Idx { impl Idx { /// Creates a new index from a [`RawIdx`]. - pub fn from_raw(raw: RawIdx) -> Self { + pub const fn from_raw(raw: RawIdx) -> Self { Idx { raw, _ty: PhantomData } } /// Converts this index into the underlying [`RawIdx`]. - pub fn into_raw(self) -> RawIdx { + pub const fn into_raw(self) -> RawIdx { self.raw } } @@ -147,13 +174,46 @@ impl IdxRange { pub fn is_empty(&self) -> bool { self.range.is_empty() } + + /// Returns the start of the index range. + pub fn start(&self) -> Idx { + Idx::from_raw(RawIdx::from(self.range.start)) + } + + /// Returns the end of the index range. + pub fn end(&self) -> Idx { + Idx::from_raw(RawIdx::from(self.range.end)) + } } impl Iterator for IdxRange { type Item = Idx; + fn next(&mut self) -> Option { self.range.next().map(|raw| Idx::from_raw(raw.into())) } + + fn size_hint(&self) -> (usize, Option) { + self.range.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.range.count() + } + + fn last(self) -> Option + where + Self: Sized, + { + self.range.last().map(|raw| Idx::from_raw(raw.into())) + } + + fn nth(&mut self, n: usize) -> Option { + self.range.nth(n).map(|raw| Idx::from_raw(raw.into())) + } } impl DoubleEndedIterator for IdxRange { @@ -162,6 +222,10 @@ impl DoubleEndedIterator for IdxRange { } } +impl ExactSizeIterator for IdxRange {} + +impl FusedIterator for IdxRange {} + impl fmt::Debug for IdxRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple(&format!("IdxRange::<{}>", std::any::type_name::())) @@ -280,6 +344,21 @@ impl Arena { idx } + /// Densely allocates multiple values, returning the values’ index range. + /// + /// ``` + /// let mut arena = la_arena::Arena::new(); + /// let range = arena.alloc_many(0..4); + /// + /// assert_eq!(arena[range], [0, 1, 2, 3]); + /// ``` + pub fn alloc_many>(&mut self, iter: II) -> IdxRange { + let start = self.next_idx(); + self.extend(iter); + let end = self.next_idx(); + IdxRange::new(start..end) + } + /// Returns an iterator over the arena’s elements. /// /// ``` @@ -295,7 +374,7 @@ impl Arena { /// ``` pub fn iter( &self, - ) -> impl Iterator, &T)> + ExactSizeIterator + DoubleEndedIterator { + ) -> impl Iterator, &T)> + ExactSizeIterator + DoubleEndedIterator + Clone { self.data.iter().enumerate().map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value)) } @@ -335,7 +414,7 @@ impl Arena { /// assert_eq!(iterator.next(), Some(&40)); /// assert_eq!(iterator.next(), Some(&60)); /// ``` - pub fn values(&mut self) -> impl Iterator + ExactSizeIterator + DoubleEndedIterator { + pub fn values(&self) -> impl Iterator + ExactSizeIterator + DoubleEndedIterator { self.data.iter() } @@ -372,6 +451,12 @@ impl Arena { } } +impl AsMut<[T]> for Arena { + fn as_mut(&mut self) -> &mut [T] { + self.data.as_mut() + } +} + impl Default for Arena { fn default() -> Arena { Arena { data: Vec::new() } @@ -410,3 +495,32 @@ impl FromIterator for Arena { Arena { data: Vec::from_iter(iter) } } } + +/// An iterator over the arena’s elements. +pub struct IntoIter(Enumerate< as IntoIterator>::IntoIter>); + +impl Iterator for IntoIter { + type Item = (Idx, T); + + fn next(&mut self) -> Option { + self.0.next().map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value)) + } +} + +impl IntoIterator for Arena { + type Item = (Idx, T); + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter(self.data.into_iter().enumerate()) + } +} + +impl Extend for Arena { + fn extend>(&mut self, iter: II) { + for t in iter { + self.alloc(t); + } + } +} diff --git a/src/tools/rust-analyzer/lib/la-arena/src/map.rs b/src/tools/rust-analyzer/lib/la-arena/src/map.rs index 7fff2b09c..750f345b5 100644 --- a/src/tools/rust-analyzer/lib/la-arena/src/map.rs +++ b/src/tools/rust-analyzer/lib/la-arena/src/map.rs @@ -1,3 +1,4 @@ +use std::iter::Enumerate; use std::marker::PhantomData; use crate::Idx; @@ -72,17 +73,17 @@ impl ArenaMap, V> { } /// Returns an iterator over the values in the map. - pub fn values(&self) -> impl Iterator { + pub fn values(&self) -> impl Iterator + DoubleEndedIterator { 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 { + pub fn values_mut(&mut self) -> impl Iterator + DoubleEndedIterator { 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, &V)> { + pub fn iter(&self) -> impl Iterator, &V)> + DoubleEndedIterator { self.v.iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_ref()?))) } @@ -94,12 +95,6 @@ impl ArenaMap, V> { .filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_mut()?))) } - /// Returns an iterator over the arena indexes and values in the map. - // FIXME: Implement `IntoIterator` trait. - pub fn into_iter(self) -> impl Iterator, V)> { - self.v.into_iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o?))) - } - /// Gets the given key's corresponding entry in the map for in-place manipulation. pub fn entry(&mut self, idx: Idx) -> Entry<'_, Idx, V> { let idx = Self::to_idx(idx); @@ -154,6 +149,63 @@ impl FromIterator<(Idx, T)> for ArenaMap, T> { } } +pub struct ArenaMapIter { + iter: Enumerate>>, + _ty: PhantomData, +} + +impl IntoIterator for ArenaMap, V> { + type Item = (Idx, V); + + type IntoIter = ArenaMapIter, V>; + + fn into_iter(self) -> Self::IntoIter { + let iter = self.v.into_iter().enumerate(); + Self::IntoIter { iter, _ty: PhantomData } + } +} + +impl ArenaMapIter, V> { + fn mapper((idx, o): (usize, Option)) -> Option<(Idx, V)> { + Some((ArenaMap::, V>::from_idx(idx), o?)) + } +} + +impl Iterator for ArenaMapIter, V> { + type Item = (Idx, V); + + #[inline] + fn next(&mut self) -> Option { + for next in self.iter.by_ref() { + match Self::mapper(next) { + Some(r) => return Some(r), + None => continue, + } + } + + None + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl DoubleEndedIterator for ArenaMapIter, V> { + #[inline] + fn next_back(&mut self) -> Option { + while let Some(next_back) = self.iter.next_back() { + match Self::mapper(next_back) { + Some(r) => return Some(r), + None => continue, + } + } + + None + } +} + /// A view into a single entry in a map, which may either be vacant or occupied. /// /// This `enum` is constructed from the [`entry`] method on [`ArenaMap`]. diff --git a/src/tools/rust-analyzer/lib/line-index/Cargo.toml b/src/tools/rust-analyzer/lib/line-index/Cargo.toml new file mode 100644 index 000000000..019ad3a53 --- /dev/null +++ b/src/tools/rust-analyzer/lib/line-index/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "line-index" +version = "0.1.0-pre.1" +description = "Maps flat `TextSize` offsets to/from `(line, column)` representation." +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/line-index" +edition = "2021" + +[dependencies] +text-size.workspace = true +nohash-hasher.workspace = true diff --git a/src/tools/rust-analyzer/lib/line-index/src/lib.rs b/src/tools/rust-analyzer/lib/line-index/src/lib.rs new file mode 100644 index 000000000..ad67d3f24 --- /dev/null +++ b/src/tools/rust-analyzer/lib/line-index/src/lib.rs @@ -0,0 +1,237 @@ +//! See [`LineIndex`]. + +#![deny(missing_debug_implementations, missing_docs, rust_2018_idioms)] + +#[cfg(test)] +mod tests; + +use nohash_hasher::IntMap; + +pub use text_size::{TextRange, TextSize}; + +/// `(line, column)` information in the native, UTF-8 encoding. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LineCol { + /// Zero-based. + pub line: u32, + /// Zero-based UTF-8 offset. + pub col: u32, +} + +/// A kind of wide character encoding. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum WideEncoding { + /// UTF-16. + Utf16, + /// UTF-32. + Utf32, +} + +impl WideEncoding { + /// Returns the number of code units it takes to encode `text` in this encoding. + pub fn measure(&self, text: &str) -> usize { + match self { + WideEncoding::Utf16 => text.encode_utf16().count(), + WideEncoding::Utf32 => text.chars().count(), + } + } +} + +/// `(line, column)` information in wide encodings. +/// +/// See [`WideEncoding`] for the kinds of wide encodings available. +// +// Deliberately not a generic type and different from `LineCol`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct WideLineCol { + /// Zero-based. + pub line: u32, + /// Zero-based. + pub col: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct WideChar { + /// Start offset of a character inside a line, zero-based. + start: TextSize, + /// End offset of a character inside a line, zero-based. + end: TextSize, +} + +impl WideChar { + /// Returns the length in 8-bit UTF-8 code units. + fn len(&self) -> TextSize { + self.end - self.start + } + + /// Returns the length in UTF-16 or UTF-32 code units. + fn wide_len(&self, enc: WideEncoding) -> u32 { + match enc { + WideEncoding::Utf16 => { + if self.len() == TextSize::from(4) { + 2 + } else { + 1 + } + } + WideEncoding::Utf32 => 1, + } + } +} + +/// Maps flat [`TextSize`] offsets to/from `(line, column)` representation. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LineIndex { + /// Offset the beginning of each line (except the first, which always has offset 0). + newlines: Box<[TextSize]>, + /// List of non-ASCII characters on each line. + line_wide_chars: IntMap>, + /// The length of the entire text. + len: TextSize, +} + +impl LineIndex { + /// Returns a `LineIndex` for the `text`. + pub fn new(text: &str) -> LineIndex { + let mut newlines = Vec::::with_capacity(16); + let mut line_wide_chars = IntMap::>::default(); + + let mut wide_chars = Vec::::new(); + let mut cur_row = TextSize::from(0); + let mut cur_col = TextSize::from(0); + let mut line = 0u32; + + for c in text.chars() { + let c_len = TextSize::of(c); + cur_row += c_len; + if c == '\n' { + newlines.push(cur_row); + + // Save any wide characters seen in the previous line + if !wide_chars.is_empty() { + let cs = std::mem::take(&mut wide_chars).into_boxed_slice(); + line_wide_chars.insert(line, cs); + } + + // Prepare for processing the next line + cur_col = TextSize::from(0); + line += 1; + continue; + } + + if !c.is_ascii() { + wide_chars.push(WideChar { start: cur_col, end: cur_col + c_len }); + } + + cur_col += c_len; + } + + // Save any wide characters seen in the last line + if !wide_chars.is_empty() { + line_wide_chars.insert(line, wide_chars.into_boxed_slice()); + } + + LineIndex { + newlines: newlines.into_boxed_slice(), + line_wide_chars, + len: TextSize::of(text), + } + } + + /// Transforms the `TextSize` into a `LineCol`. + /// + /// # Panics + /// + /// If the offset is invalid. See [`Self::try_line_col`]. + pub fn line_col(&self, offset: TextSize) -> LineCol { + self.try_line_col(offset).expect("invalid offset") + } + + /// Transforms the `TextSize` into a `LineCol`. + /// + /// Returns `None` if the `offset` was invalid, e.g. if it extends past the end of the text or + /// points to the middle of a multi-byte character. + pub fn try_line_col(&self, offset: TextSize) -> Option { + if offset > self.len { + return None; + } + let line = self.newlines.partition_point(|&it| it <= offset); + let start = self.start_offset(line)?; + let col = offset - start; + let ret = LineCol { line: line as u32, col: col.into() }; + self.line_wide_chars + .get(&ret.line) + .into_iter() + .flat_map(|it| it.iter()) + .all(|it| col <= it.start || it.end <= col) + .then_some(ret) + } + + /// Transforms the `LineCol` into a `TextSize`. + pub fn offset(&self, line_col: LineCol) -> Option { + self.start_offset(line_col.line as usize).map(|start| start + TextSize::from(line_col.col)) + } + + fn start_offset(&self, line: usize) -> Option { + match line.checked_sub(1) { + None => Some(TextSize::from(0)), + Some(it) => self.newlines.get(it).copied(), + } + } + + /// Transforms the `LineCol` with the given `WideEncoding` into a `WideLineCol`. + pub fn to_wide(&self, enc: WideEncoding, line_col: LineCol) -> Option { + let mut col = line_col.col; + if let Some(wide_chars) = self.line_wide_chars.get(&line_col.line) { + for c in wide_chars.iter() { + if u32::from(c.end) <= line_col.col { + col = col.checked_sub(u32::from(c.len()) - c.wide_len(enc))?; + } else { + // From here on, all utf16 characters come *after* the character we are mapping, + // so we don't need to take them into account + break; + } + } + } + Some(WideLineCol { line: line_col.line, col }) + } + + /// Transforms the `WideLineCol` with the given `WideEncoding` into a `LineCol`. + pub fn to_utf8(&self, enc: WideEncoding, line_col: WideLineCol) -> Option { + let mut col = line_col.col; + if let Some(wide_chars) = self.line_wide_chars.get(&line_col.line) { + for c in wide_chars.iter() { + if col > u32::from(c.start) { + col = col.checked_add(u32::from(c.len()) - c.wide_len(enc))?; + } else { + // From here on, all utf16 characters come *after* the character we are mapping, + // so we don't need to take them into account + break; + } + } + } + Some(LineCol { line: line_col.line, col }) + } + + /// Given a range [start, end), returns a sorted iterator of non-empty ranges [start, x1), [x1, + /// x2), ..., [xn, end) where all the xi, which are positions of newlines, are inside the range + /// [start, end). + pub fn lines(&self, range: TextRange) -> impl Iterator + '_ { + let lo = self.newlines.partition_point(|&it| it < range.start()); + let hi = self.newlines.partition_point(|&it| it <= range.end()); + let all = std::iter::once(range.start()) + .chain(self.newlines[lo..hi].iter().copied()) + .chain(std::iter::once(range.end())); + + all.clone() + .zip(all.skip(1)) + .map(|(lo, hi)| TextRange::new(lo, hi)) + .filter(|it| !it.is_empty()) + } + + /// Returns the length of the original text. + pub fn len(&self) -> TextSize { + self.len + } +} diff --git a/src/tools/rust-analyzer/lib/line-index/src/tests.rs b/src/tools/rust-analyzer/lib/line-index/src/tests.rs new file mode 100644 index 000000000..31c01c20e --- /dev/null +++ b/src/tools/rust-analyzer/lib/line-index/src/tests.rs @@ -0,0 +1,11 @@ +use super::LineIndex; + +#[test] +fn test_empty_index() { + let col_index = LineIndex::new( + " +const C: char = 'x'; +", + ); + assert_eq!(col_index.line_wide_chars.len(), 0); +} diff --git a/src/tools/rust-analyzer/lib/line-index/tests/it.rs b/src/tools/rust-analyzer/lib/line-index/tests/it.rs new file mode 100644 index 000000000..ce1c0bc6f --- /dev/null +++ b/src/tools/rust-analyzer/lib/line-index/tests/it.rs @@ -0,0 +1,62 @@ +use line_index::{LineCol, LineIndex, TextRange}; + +#[test] +fn test_line_index() { + let text = "hello\nworld"; + let table = [ + (00, 0, 0), + (01, 0, 1), + (05, 0, 5), + (06, 1, 0), + (07, 1, 1), + (08, 1, 2), + (10, 1, 4), + (11, 1, 5), + ]; + + let index = LineIndex::new(text); + for (offset, line, col) in table { + assert_eq!(index.line_col(offset.into()), LineCol { line, col }); + } + + let text = "\nhello\nworld"; + let table = [(0, 0, 0), (1, 1, 0), (2, 1, 1), (6, 1, 5), (7, 2, 0)]; + let index = LineIndex::new(text); + for (offset, line, col) in table { + assert_eq!(index.line_col(offset.into()), LineCol { line, col }); + } +} + +#[test] +fn test_char_len() { + assert_eq!('メ'.len_utf8(), 3); + assert_eq!('メ'.len_utf16(), 1); +} + +#[test] +fn test_splitlines() { + fn r(lo: u32, hi: u32) -> TextRange { + TextRange::new(lo.into(), hi.into()) + } + + let text = "a\nbb\nccc\n"; + let line_index = LineIndex::new(text); + + let actual = line_index.lines(r(0, 9)).collect::>(); + let expected = vec![r(0, 2), r(2, 5), r(5, 9)]; + assert_eq!(actual, expected); + + let text = ""; + let line_index = LineIndex::new(text); + + let actual = line_index.lines(r(0, 0)).collect::>(); + let expected = vec![]; + assert_eq!(actual, expected); + + let text = "\n"; + let line_index = LineIndex::new(text); + + let actual = line_index.lines(r(0, 1)).collect::>(); + let expected = vec![r(0, 1)]; + assert_eq!(actual, expected) +} diff --git a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml index 6e32e3960..e78a9d2eb 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml +++ b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" [dependencies] log = "0.4.17" -serde_json = "1.0.86" -serde = { version = "1.0.144", features = ["derive"] } +serde_json.workspace = true +serde.workspace = true crossbeam-channel = "0.5.6" [dev-dependencies] diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/error.rs b/src/tools/rust-analyzer/lib/lsp-server/src/error.rs index 4c934d9ec..755b3fd95 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/src/error.rs +++ b/src/tools/rust-analyzer/lib/lsp-server/src/error.rs @@ -2,7 +2,7 @@ use std::fmt; use crate::{Notification, Request}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ProtocolError(pub(crate) String); impl std::error::Error for ProtocolError {} diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs b/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs index beccde40a..affab60a2 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs +++ b/src/tools/rust-analyzer/lib/lsp-server/src/lib.rs @@ -126,6 +126,9 @@ impl Connection { self.sender.send(resp.into()).unwrap(); continue; } + Ok(Message::Notification(n)) if !n.is_exit() => { + continue; + } Ok(msg) => Err(ProtocolError(format!("expected initialize request, got {msg:?}"))), Err(e) => { Err(ProtocolError(format!("expected initialize request, got error: {e}"))) @@ -212,3 +215,70 @@ impl Connection { Ok(true) } } + +#[cfg(test)] +mod tests { + use crossbeam_channel::unbounded; + use lsp_types::notification::{Exit, Initialized, Notification}; + use lsp_types::request::{Initialize, Request}; + use lsp_types::{InitializeParams, InitializedParams}; + use serde_json::to_value; + + use crate::{Connection, Message, ProtocolError, RequestId}; + + struct TestCase { + test_messages: Vec, + expected_resp: Result<(RequestId, serde_json::Value), ProtocolError>, + } + + fn initialize_start_test(test_case: TestCase) { + let (reader_sender, reader_receiver) = unbounded::(); + let (writer_sender, writer_receiver) = unbounded::(); + let conn = Connection { sender: writer_sender, receiver: reader_receiver }; + + for msg in test_case.test_messages { + assert!(reader_sender.send(msg).is_ok()); + } + + let resp = conn.initialize_start(); + assert_eq!(test_case.expected_resp, resp); + + assert!(writer_receiver.recv_timeout(std::time::Duration::from_secs(1)).is_err()); + } + + #[test] + fn not_exit_notification() { + let notification = crate::Notification { + method: Initialized::METHOD.to_string(), + params: to_value(InitializedParams {}).unwrap(), + }; + + let params_as_value = to_value(InitializeParams::default()).unwrap(); + let req_id = RequestId::from(234); + let request = crate::Request { + id: req_id.clone(), + method: Initialize::METHOD.to_string(), + params: params_as_value.clone(), + }; + + initialize_start_test(TestCase { + test_messages: vec![notification.into(), request.into()], + expected_resp: Ok((req_id, params_as_value)), + }); + } + + #[test] + fn exit_notification() { + let notification = + crate::Notification { method: Exit::METHOD.to_string(), params: to_value(()).unwrap() }; + let notification_msg = Message::from(notification); + + initialize_start_test(TestCase { + test_messages: vec![notification_msg.clone()], + expected_resp: Err(ProtocolError(format!( + "expected initialize request, got {:?}", + notification_msg + ))), + }); + } +} -- cgit v1.2.3