Skip to content

Commit 7e1992a

Browse files
committed
Make line-index an external lib
1 parent 29256f2 commit 7e1992a

File tree

7 files changed

+177
-142
lines changed

7 files changed

+177
-142
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ide-db/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ text-edit.workspace = true
3737
# something from some `hir-xxx` subpackage, reexport the API via `hir`.
3838
hir.workspace = true
3939

40+
# used to be a module, turned into its own library
41+
line-index = { version = "0.1.0", path = "../../lib/line-index" }
42+
4043
[dev-dependencies]
4144
expect-test = "1.4.0"
4245
oorandom = "11.1.3"

crates/ide-db/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ pub mod famous_defs;
1313
pub mod helpers;
1414
pub mod items_locator;
1515
pub mod label;
16-
pub mod line_index;
1716
pub mod path_transform;
1817
pub mod rename;
1918
pub mod rust_doc;
@@ -55,6 +54,8 @@ use triomphe::Arc;
5554
use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
5655
pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
5756

57+
pub use ::line_index;
58+
5859
/// `base_db` is normally also needed in places where `ide_db` is used, so this re-export is for convenience.
5960
pub use base_db;
6061

@@ -414,4 +415,5 @@ impl SnippetCap {
414415
#[cfg(test)]
415416
mod tests {
416417
mod sourcegen_lints;
418+
mod line_index;
417419
}

crates/ide-db/src/tests/line_index.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use line_index::{LineCol, LineIndex, WideEncoding};
2+
use test_utils::skip_slow_tests;
3+
4+
#[test]
5+
fn test_every_chars() {
6+
if skip_slow_tests() {
7+
return;
8+
}
9+
10+
let text: String = {
11+
let mut chars: Vec<char> = ((0 as char)..char::MAX).collect(); // Neat!
12+
chars.extend("\n".repeat(chars.len() / 16).chars());
13+
let mut rng = oorandom::Rand32::new(stdx::rand::seed());
14+
stdx::rand::shuffle(&mut chars, |i| rng.rand_range(0..i as u32) as usize);
15+
chars.into_iter().collect()
16+
};
17+
assert!(text.contains('💩')); // Sanity check.
18+
19+
let line_index = LineIndex::new(&text);
20+
21+
let mut lin_col = LineCol { line: 0, col: 0 };
22+
let mut col_utf16 = 0;
23+
let mut col_utf32 = 0;
24+
for (offset, c) in text.char_indices() {
25+
let got_offset = line_index.offset(lin_col).unwrap();
26+
assert_eq!(usize::from(got_offset), offset);
27+
28+
let got_lin_col = line_index.line_col(got_offset);
29+
assert_eq!(got_lin_col, lin_col);
30+
31+
for enc in [WideEncoding::Utf16, WideEncoding::Utf32] {
32+
let wide_lin_col = line_index.to_wide(enc, lin_col);
33+
let got_lin_col = line_index.to_utf8(enc, wide_lin_col);
34+
assert_eq!(got_lin_col, lin_col);
35+
36+
let want_col = match enc {
37+
WideEncoding::Utf16 => col_utf16,
38+
WideEncoding::Utf32 => col_utf32,
39+
};
40+
assert_eq!(wide_lin_col.col, want_col)
41+
}
42+
43+
if c == '\n' {
44+
lin_col.line += 1;
45+
lin_col.col = 0;
46+
col_utf16 = 0;
47+
col_utf32 = 0;
48+
} else {
49+
lin_col.col += c.len_utf8() as u32;
50+
col_utf16 += c.len_utf16() as u32;
51+
col_utf32 += 1;
52+
}
53+
}
54+
}

lib/line-index/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "line-index"
3+
version = "0.1.0"
4+
description = "Maps flat `TextSize` offsets into `(line, column)` representation."
5+
license = "MIT OR Apache-2.0"
6+
repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/non-hash"
7+
edition = "2021"
8+
9+
[dependencies]
10+
text-size = "1"
11+
non-hash = { version = "0.1.0", path = "../non-hash" }
Lines changed: 24 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
//! `LineIndex` maps flat `TextSize` offsets into `(Line, Column)`
2-
//! representation.
1+
//! See [`LineIndex`].
2+
3+
#![deny(clippy::pedantic, missing_debug_implementations, missing_docs, rust_2018_idioms)]
4+
5+
#[cfg(test)]
6+
mod tests;
7+
38
use std::{iter, mem};
49

5-
use stdx::hash::NoHashHashMap;
6-
use syntax::{TextRange, TextSize};
10+
use non_hash::NoHashHashMap;
11+
use text_size::{TextRange, TextSize};
712

13+
/// Maps flat [`TextSize`] offsets into `(line, column)` representation.
814
#[derive(Clone, Debug, PartialEq, Eq)]
915
pub struct LineIndex {
1016
/// Offset the beginning of each line, zero-based.
@@ -16,26 +22,29 @@ pub struct LineIndex {
1622
/// Line/Column information in native, utf8 format.
1723
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1824
pub struct LineCol {
19-
/// Zero-based
25+
/// Zero-based.
2026
pub line: u32,
21-
/// Zero-based utf8 offset
27+
/// Zero-based UTF-8 offset.
2228
pub col: u32,
2329
}
2430

31+
/// A kind of wide character encoding.
2532
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2633
pub enum WideEncoding {
34+
/// UTF-16.
2735
Utf16,
36+
/// UTF-32.
2837
Utf32,
2938
}
3039

3140
/// Line/Column information in legacy encodings.
3241
///
33-
/// Deliberately not a generic type and different from `LineCol`.
42+
/// Deliberately not a generic type and different from [`LineCol`].
3443
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3544
pub struct WideLineCol {
36-
/// Zero-based
45+
/// Zero-based.
3746
pub line: u32,
38-
/// Zero-based
47+
/// Zero-based.
3948
pub col: u32,
4049
}
4150

@@ -70,6 +79,7 @@ impl WideChar {
7079
}
7180

7281
impl LineIndex {
82+
/// Returns a `LineIndex` for the `text`.
7383
pub fn new(text: &str) -> LineIndex {
7484
let mut line_wide_chars = NoHashHashMap::default();
7585
let mut wide_chars = Vec::new();
@@ -115,29 +125,34 @@ impl LineIndex {
115125
LineIndex { newlines, line_wide_chars }
116126
}
117127

128+
/// Transforms the `TextSize` into a `LineCol`.
118129
pub fn line_col(&self, offset: TextSize) -> LineCol {
119130
let line = self.newlines.partition_point(|&it| it <= offset) - 1;
120131
let line_start_offset = self.newlines[line];
121132
let col = offset - line_start_offset;
122133
LineCol { line: line as u32, col: col.into() }
123134
}
124135

136+
/// Transforms the `LineCol` into a `TextSize`.
125137
pub fn offset(&self, line_col: LineCol) -> Option<TextSize> {
126138
self.newlines
127139
.get(line_col.line as usize)
128140
.map(|offset| offset + TextSize::from(line_col.col))
129141
}
130142

143+
/// Transforms the `LineCol` with the given `WideEncoding` into a `WideLineCol`.
131144
pub fn to_wide(&self, enc: WideEncoding, line_col: LineCol) -> WideLineCol {
132145
let col = self.utf8_to_wide_col(enc, line_col.line, line_col.col.into());
133146
WideLineCol { line: line_col.line, col: col as u32 }
134147
}
135148

149+
/// Transforms the `WideLineCol` with the given `WideEncoding` into a `LineCol`.
136150
pub fn to_utf8(&self, enc: WideEncoding, line_col: WideLineCol) -> LineCol {
137151
let col = self.wide_to_utf8_col(enc, line_col.line, line_col.col);
138152
LineCol { line: line_col.line, col: col.into() }
139153
}
140154

155+
/// Returns an iterator over the ranges for the lines.
141156
pub fn lines(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
142157
let lo = self.newlines.partition_point(|&it| it < range.start());
143158
let hi = self.newlines.partition_point(|&it| it <= range.end());
@@ -183,135 +198,3 @@ impl LineIndex {
183198
col.into()
184199
}
185200
}
186-
187-
#[cfg(test)]
188-
mod tests {
189-
use test_utils::skip_slow_tests;
190-
191-
use super::WideEncoding::{Utf16, Utf32};
192-
use super::*;
193-
194-
#[test]
195-
fn test_line_index() {
196-
let text = "hello\nworld";
197-
let table = [
198-
(00, 0, 0),
199-
(01, 0, 1),
200-
(05, 0, 5),
201-
(06, 1, 0),
202-
(07, 1, 1),
203-
(08, 1, 2),
204-
(10, 1, 4),
205-
(11, 1, 5),
206-
(12, 1, 6),
207-
];
208-
209-
let index = LineIndex::new(text);
210-
for (offset, line, col) in table {
211-
assert_eq!(index.line_col(offset.into()), LineCol { line, col });
212-
}
213-
214-
let text = "\nhello\nworld";
215-
let table = [(0, 0, 0), (1, 1, 0), (2, 1, 1), (6, 1, 5), (7, 2, 0)];
216-
let index = LineIndex::new(text);
217-
for (offset, line, col) in table {
218-
assert_eq!(index.line_col(offset.into()), LineCol { line, col });
219-
}
220-
}
221-
222-
#[test]
223-
fn test_char_len() {
224-
assert_eq!('メ'.len_utf8(), 3);
225-
assert_eq!('メ'.len_utf16(), 1);
226-
}
227-
228-
#[test]
229-
fn test_empty_index() {
230-
let col_index = LineIndex::new(
231-
"
232-
const C: char = 'x';
233-
",
234-
);
235-
assert_eq!(col_index.line_wide_chars.len(), 0);
236-
}
237-
238-
#[test]
239-
fn test_every_chars() {
240-
if skip_slow_tests() {
241-
return;
242-
}
243-
244-
let text: String = {
245-
let mut chars: Vec<char> = ((0 as char)..char::MAX).collect(); // Neat!
246-
chars.extend("\n".repeat(chars.len() / 16).chars());
247-
let mut rng = oorandom::Rand32::new(stdx::rand::seed());
248-
stdx::rand::shuffle(&mut chars, |i| rng.rand_range(0..i as u32) as usize);
249-
chars.into_iter().collect()
250-
};
251-
assert!(text.contains('💩')); // Sanity check.
252-
253-
let line_index = LineIndex::new(&text);
254-
255-
let mut lin_col = LineCol { line: 0, col: 0 };
256-
let mut col_utf16 = 0;
257-
let mut col_utf32 = 0;
258-
for (offset, c) in text.char_indices() {
259-
let got_offset = line_index.offset(lin_col).unwrap();
260-
assert_eq!(usize::from(got_offset), offset);
261-
262-
let got_lin_col = line_index.line_col(got_offset);
263-
assert_eq!(got_lin_col, lin_col);
264-
265-
for enc in [Utf16, Utf32] {
266-
let wide_lin_col = line_index.to_wide(enc, lin_col);
267-
let got_lin_col = line_index.to_utf8(enc, wide_lin_col);
268-
assert_eq!(got_lin_col, lin_col);
269-
270-
let want_col = match enc {
271-
Utf16 => col_utf16,
272-
Utf32 => col_utf32,
273-
};
274-
assert_eq!(wide_lin_col.col, want_col)
275-
}
276-
277-
if c == '\n' {
278-
lin_col.line += 1;
279-
lin_col.col = 0;
280-
col_utf16 = 0;
281-
col_utf32 = 0;
282-
} else {
283-
lin_col.col += c.len_utf8() as u32;
284-
col_utf16 += c.len_utf16() as u32;
285-
col_utf32 += 1;
286-
}
287-
}
288-
}
289-
290-
#[test]
291-
fn test_splitlines() {
292-
fn r(lo: u32, hi: u32) -> TextRange {
293-
TextRange::new(lo.into(), hi.into())
294-
}
295-
296-
let text = "a\nbb\nccc\n";
297-
let line_index = LineIndex::new(text);
298-
299-
let actual = line_index.lines(r(0, 9)).collect::<Vec<_>>();
300-
let expected = vec![r(0, 2), r(2, 5), r(5, 9)];
301-
assert_eq!(actual, expected);
302-
303-
let text = "";
304-
let line_index = LineIndex::new(text);
305-
306-
let actual = line_index.lines(r(0, 0)).collect::<Vec<_>>();
307-
let expected = vec![];
308-
assert_eq!(actual, expected);
309-
310-
let text = "\n";
311-
let line_index = LineIndex::new(text);
312-
313-
let actual = line_index.lines(r(0, 1)).collect::<Vec<_>>();
314-
let expected = vec![r(0, 1)];
315-
assert_eq!(actual, expected)
316-
}
317-
}

0 commit comments

Comments
 (0)