summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:57:31 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:57:31 +0000
commitdc0db358abe19481e475e10c32149b53370f1a1c (patch)
treeab8ce99c4b255ce46f99ef402c27916055b899ee /src/tools/rust-analyzer/lib
parentReleasing progress-linux version 1.71.1+dfsg1-2~progress7.99u1. (diff)
downloadrustc-dc0db358abe19481e475e10c32149b53370f1a1c.tar.xz
rustc-dc0db358abe19481e475e10c32149b53370f1a1c.zip
Merging upstream version 1.72.1+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.md7
-rw-r--r--src/tools/rust-analyzer/lib/la-arena/src/lib.rs124
-rw-r--r--src/tools/rust-analyzer/lib/la-arena/src/map.rs70
-rw-r--r--src/tools/rust-analyzer/lib/line-index/Cargo.toml11
-rw-r--r--src/tools/rust-analyzer/lib/line-index/src/lib.rs237
-rw-r--r--src/tools/rust-analyzer/lib/line-index/src/tests.rs11
-rw-r--r--src/tools/rust-analyzer/lib/line-index/tests/it.rs62
-rw-r--r--src/tools/rust-analyzer/lib/lsp-server/Cargo.toml4
-rw-r--r--src/tools/rust-analyzer/lib/lsp-server/src/error.rs2
-rw-r--r--src/tools/rust-analyzer/lib/lsp-server/src/lib.rs70
10 files changed, 579 insertions, 19 deletions
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<RawIdx> for u32 {
+ #[inline]
fn from(raw: RawIdx) -> u32 {
raw.0
}
}
impl From<u32> for RawIdx {
+ #[inline]
fn from(idx: u32) -> RawIdx {
RawIdx(idx)
}
@@ -47,6 +62,18 @@ pub struct Idx<T> {
_ty: PhantomData<fn() -> T>,
}
+impl<T> Ord for Idx<T> {
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ self.raw.cmp(&other.raw)
+ }
+}
+
+impl<T> PartialOrd for Idx<T> {
+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+ self.raw.partial_cmp(&other.raw)
+ }
+}
+
impl<T> Clone for Idx<T> {
fn clone(&self) -> Self {
*self
@@ -79,12 +106,12 @@ impl<T> fmt::Debug for Idx<T> {
impl<T> Idx<T> {
/// 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<T> IdxRange<T> {
pub fn is_empty(&self) -> bool {
self.range.is_empty()
}
+
+ /// Returns the start of the index range.
+ pub fn start(&self) -> Idx<T> {
+ Idx::from_raw(RawIdx::from(self.range.start))
+ }
+
+ /// Returns the end of the index range.
+ pub fn end(&self) -> Idx<T> {
+ Idx::from_raw(RawIdx::from(self.range.end))
+ }
}
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()))
}
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.range.size_hint()
+ }
+
+ fn count(self) -> usize
+ where
+ Self: Sized,
+ {
+ self.range.count()
+ }
+
+ fn last(self) -> Option<Self::Item>
+ where
+ Self: Sized,
+ {
+ self.range.last().map(|raw| Idx::from_raw(raw.into()))
+ }
+
+ fn nth(&mut self, n: usize) -> Option<Self::Item> {
+ self.range.nth(n).map(|raw| Idx::from_raw(raw.into()))
+ }
}
impl<T> DoubleEndedIterator for IdxRange<T> {
@@ -162,6 +222,10 @@ impl<T> DoubleEndedIterator for IdxRange<T> {
}
}
+impl<T> ExactSizeIterator for IdxRange<T> {}
+
+impl<T> FusedIterator for IdxRange<T> {}
+
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>()))
@@ -280,6 +344,21 @@ impl<T> Arena<T> {
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<II: IntoIterator<Item = T>>(&mut self, iter: II) -> IdxRange<T> {
+ 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<T> Arena<T> {
/// ```
pub fn iter(
&self,
- ) -> impl Iterator<Item = (Idx<T>, &T)> + ExactSizeIterator + DoubleEndedIterator {
+ ) -> impl Iterator<Item = (Idx<T>, &T)> + ExactSizeIterator + DoubleEndedIterator + Clone {
self.data.iter().enumerate().map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value))
}
@@ -335,7 +414,7 @@ impl<T> Arena<T> {
/// assert_eq!(iterator.next(), Some(&40));
/// assert_eq!(iterator.next(), Some(&60));
/// ```
- pub fn values(&mut self) -> impl Iterator<Item = &T> + ExactSizeIterator + DoubleEndedIterator {
+ pub fn values(&self) -> impl Iterator<Item = &T> + ExactSizeIterator + DoubleEndedIterator {
self.data.iter()
}
@@ -372,6 +451,12 @@ impl<T> Arena<T> {
}
}
+impl<T> AsMut<[T]> for Arena<T> {
+ fn as_mut(&mut self) -> &mut [T] {
+ self.data.as_mut()
+ }
+}
+
impl<T> Default for Arena<T> {
fn default() -> Arena<T> {
Arena { data: Vec::new() }
@@ -410,3 +495,32 @@ impl<T> FromIterator<T> for Arena<T> {
Arena { data: Vec::from_iter(iter) }
}
}
+
+/// An iterator over the arena’s elements.
+pub struct IntoIter<T>(Enumerate<<Vec<T> as IntoIterator>::IntoIter>);
+
+impl<T> Iterator for IntoIter<T> {
+ type Item = (Idx<T>, T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next().map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value))
+ }
+}
+
+impl<T> IntoIterator for Arena<T> {
+ type Item = (Idx<T>, T);
+
+ type IntoIter = IntoIter<T>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ IntoIter(self.data.into_iter().enumerate())
+ }
+}
+
+impl<T> Extend<T> for Arena<T> {
+ fn extend<II: IntoIterator<Item = T>>(&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<T, V> ArenaMap<Idx<T>, V> {
}
/// Returns an iterator over the values in the map.
- pub fn values(&self) -> impl Iterator<Item = &V> {
+ pub fn values(&self) -> impl Iterator<Item = &V> + 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<Item = &mut V> {
+ pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> + 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<Item = (Idx<T>, &V)> {
+ pub fn iter(&self) -> impl Iterator<Item = (Idx<T>, &V)> + DoubleEndedIterator {
self.v.iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_ref()?)))
}
@@ -94,12 +95,6 @@ impl<T, V> ArenaMap<Idx<T>, 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<Item = (Idx<T>, 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<T>) -> Entry<'_, Idx<T>, V> {
let idx = Self::to_idx(idx);
@@ -154,6 +149,63 @@ impl<T, V> FromIterator<(Idx<V>, T)> for ArenaMap<Idx<V>, T> {
}
}
+pub struct ArenaMapIter<IDX, V> {
+ iter: Enumerate<std::vec::IntoIter<Option<V>>>,
+ _ty: PhantomData<IDX>,
+}
+
+impl<T, V> IntoIterator for ArenaMap<Idx<T>, V> {
+ type Item = (Idx<T>, V);
+
+ type IntoIter = ArenaMapIter<Idx<T>, V>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ let iter = self.v.into_iter().enumerate();
+ Self::IntoIter { iter, _ty: PhantomData }
+ }
+}
+
+impl<T, V> ArenaMapIter<Idx<T>, V> {
+ fn mapper((idx, o): (usize, Option<V>)) -> Option<(Idx<T>, V)> {
+ Some((ArenaMap::<Idx<T>, V>::from_idx(idx), o?))
+ }
+}
+
+impl<T, V> Iterator for ArenaMapIter<Idx<T>, V> {
+ type Item = (Idx<T>, V);
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+impl<T, V> DoubleEndedIterator for ArenaMapIter<Idx<T>, V> {
+ #[inline]
+ fn next_back(&mut self) -> Option<Self::Item> {
+ 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<u32, Box<[WideChar]>>,
+ /// 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::<TextSize>::with_capacity(16);
+ let mut line_wide_chars = IntMap::<u32, Box<[WideChar]>>::default();
+
+ let mut wide_chars = Vec::<WideChar>::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<LineCol> {
+ 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<TextSize> {
+ self.start_offset(line_col.line as usize).map(|start| start + TextSize::from(line_col.col))
+ }
+
+ fn start_offset(&self, line: usize) -> Option<TextSize> {
+ 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<WideLineCol> {
+ 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<LineCol> {
+ 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<Item = TextRange> + '_ {
+ 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::<Vec<_>>();
+ 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::<Vec<_>>();
+ 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::<Vec<_>>();
+ 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<Message>,
+ expected_resp: Result<(RequestId, serde_json::Value), ProtocolError>,
+ }
+
+ fn initialize_start_test(test_case: TestCase) {
+ let (reader_sender, reader_receiver) = unbounded::<Message>();
+ let (writer_sender, writer_receiver) = unbounded::<Message>();
+ 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
+ ))),
+ });
+ }
+}