Skip to content

Commit e35ac9b

Browse files
authored
Merge pull request #117 from Tcode-Motion/jules-lsp-char-lookup-opt-10062600667242564767
⚡ Optimize LSP boundary character lookups from O(N) to O(1)
2 parents d1d6430 + a807d48 commit e35ac9b

3 files changed

Lines changed: 269 additions & 1 deletion

File tree

‎Cargo.lock‎

Lines changed: 198 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tools/lsp/Cargo.toml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,10 @@ tower-lsp = { workspace = true }
2020
serde = { workspace = true }
2121
serde_json = { workspace = true }
2222
tokio = { version = "1", features = ["full"] }
23+
24+
[dev-dependencies]
25+
criterion = "0.8.2"
26+
27+
[[bench]]
28+
name = "bench"
29+
harness = false

‎tools/lsp/benches/bench.rs‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
3+
fn find_boundary_slow(line: &str, word: &str) -> usize {
4+
let mut count = 0;
5+
let word_len = word.len();
6+
let mut start_pos = 0;
7+
while let Some(pos_in_line) = line[start_pos..].find(word) {
8+
let actual_pos = start_pos + pos_in_line;
9+
let char_before = if actual_pos > 0 {
10+
line.chars().nth(actual_pos - 1)
11+
} else {
12+
None
13+
};
14+
let char_after = line.chars().nth(actual_pos + word_len);
15+
16+
let is_boundary_before = char_before.is_none_or(|c| !c.is_alphanumeric() && c != '_');
17+
let is_boundary_after = char_after.is_none_or(|c| !c.is_alphanumeric() && c != '_');
18+
19+
if is_boundary_before && is_boundary_after {
20+
count += 1;
21+
}
22+
start_pos = actual_pos + word_len;
23+
}
24+
count
25+
}
26+
27+
fn find_boundary_fast(line: &str, word: &str) -> usize {
28+
let mut count = 0;
29+
let word_len = word.len();
30+
let mut start_pos = 0;
31+
while let Some(pos_in_line) = line[start_pos..].find(word) {
32+
let actual_pos = start_pos + pos_in_line;
33+
let char_before = if actual_pos > 0 {
34+
line[..actual_pos].chars().next_back()
35+
} else {
36+
None
37+
};
38+
let char_after = line[actual_pos + word_len..].chars().next();
39+
40+
let is_boundary_before = char_before.is_none_or(|c| !c.is_alphanumeric() && c != '_');
41+
let is_boundary_after = char_after.is_none_or(|c| !c.is_alphanumeric() && c != '_');
42+
43+
if is_boundary_before && is_boundary_after {
44+
count += 1;
45+
}
46+
start_pos = actual_pos + word_len;
47+
}
48+
count
49+
}
50+
51+
fn criterion_benchmark(c: &mut Criterion) {
52+
let long_line = "let abc = 123; ".repeat(1000);
53+
let word = "abc";
54+
55+
c.bench_function("slow", |b| {
56+
b.iter(|| find_boundary_slow(std::hint::black_box(&long_line), std::hint::black_box(word)))
57+
});
58+
c.bench_function("fast", |b| {
59+
b.iter(|| find_boundary_fast(std::hint::black_box(&long_line), std::hint::black_box(word)))
60+
});
61+
}
62+
63+
criterion_group!(benches, criterion_benchmark);
64+
criterion_main!(benches);

0 commit comments

Comments
 (0)