Skip to content

Commit aa8bd3d

Browse files
authored
Merge pull request #32 from ram-nadella/perf/nucleo-matcher
perf: replace fuzzy-matcher with nucleo-matcher for improved performance
2 parents ee72bc2 + f532b83 commit aa8bd3d

5 files changed

Lines changed: 62 additions & 45 deletions

File tree

pylight/Cargo.lock

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

pylight/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ ruff_python_parser = { git = "https://github.com/astral-sh/ruff", tag = "0.12.1"
3030
ruff_python_ast = { git = "https://github.com/astral-sh/ruff", tag = "0.12.1" }
3131
ruff_text_size = { git = "https://github.com/astral-sh/ruff", tag = "0.12.1" }
3232
ruff_source_file = { git = "https://github.com/astral-sh/ruff", tag = "0.12.1" }
33-
fuzzy-matcher = "0.3"
33+
nucleo-matcher = "0.3.1"
3434
bincode = "1.3"
3535
flate2 = "1.0"
3636
fluent-uri = "0.3"

pylight/README.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ pylight/
2727
└── benches/ # Performance benchmarks
2828
```
2929

30+
## Build & Test
31+
32+
```bash
33+
# Build in release mode
34+
cargo build --release
35+
36+
# Run tests
37+
cargo test
38+
39+
# Run benchmarks
40+
cargo bench
41+
```
42+
3043
## Usage
3144

3245
### As an LSP Server
@@ -43,40 +56,28 @@ pylight
4356
pylight --standalone --directory /path/to/project --query "test"
4457
```
4558

46-
## Building
59+
## Local testing tool
4760

48-
```bash
49-
# Build in release mode
50-
cargo build --release
61+
There is tool to help with development that opens a web page where you can try the symbol search outside of VSCode
5162

52-
# Run tests
53-
cargo test
63+
Run this and open the webpage, enter the path to python code:
5464

55-
# Run benchmarks
56-
cargo bench
65+
```
66+
cargo run --release --bin pylight_devtools
5767
```
5868

5969
## Integration with VSCode
6070

61-
The `pylight` LSP server is designed to work with the `pydance` VSCode extension.
71+
The `pylight` LSP server is designed to work with the `pydance` VSCode extension.
6272
The extension will automatically start the language server when opening Python files.
6373

64-
## Performance
65-
66-
- Simple function parsing: ~7.7µs
67-
- Complex file parsing: ~72µs
68-
- Scales linearly with file size
69-
- Efficient parallel processing for large codebases
70-
7174
## Development
7275

73-
This project uses test-driven development:
74-
75-
1. Write integration tests first (`tests/integration/`)
76+
1. Write integration tests (`tests/integration/`)
7677
2. Write unit tests for components (`src/*/tests.rs`)
7778
3. Implement functionality to pass tests
7879
4. Benchmark critical paths (`benches/`)
7980

8081
## License
8182

82-
MIT
83+
MIT

pylight/src/bin/pylight_devtools.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ fn default_parser() -> String {
2929
}
3030

3131
#[derive(Serialize, Deserialize)]
32+
#[allow(dead_code)]
3233
struct SearchRequest {
3334
query: String,
3435
}
@@ -112,14 +113,18 @@ fn main() {
112113
);
113114

114115
let result = spawn_pylight(&index_req.path, &index_req.parser, pylight.clone());
115-
let response = if result.is_ok() {
116-
info!("Successfully spawned pylight for {}", index_req.path);
117-
Response::from_string(json!({"status": "success"}).to_string())
118-
} else {
119-
let err = result.unwrap_err();
120-
error!("Failed to spawn pylight: {}", err);
121-
Response::from_string(json!({"status": "error", "message": err}).to_string())
116+
let response = match result {
117+
Ok(()) => {
118+
info!("Successfully spawned pylight for {}", index_req.path);
119+
Response::from_string(json!({"status": "success"}).to_string())
120+
}
121+
Err(err) => {
122+
error!("Failed to spawn pylight: {}", err);
123+
Response::from_string(
124+
json!({"status": "error", "message": err}).to_string(),
125+
)
122126
.with_status_code(500)
127+
}
123128
};
124129
request
125130
.respond(

pylight/src/search.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
//! Symbol search functionality
22
33
use crate::symbols::Symbol;
4-
use fuzzy_matcher::skim::SkimMatcherV2;
5-
use fuzzy_matcher::FuzzyMatcher;
4+
use nucleo_matcher::pattern::{Atom, CaseMatching, Normalization};
5+
use nucleo_matcher::{Config, Matcher, Utf32Str};
66
use std::sync::Arc;
77

88
pub struct SearchEngine {
9-
matcher: SkimMatcherV2,
9+
matcher: Matcher,
1010
}
1111

1212
#[derive(Debug)]
@@ -17,8 +17,10 @@ pub struct SearchResult {
1717

1818
impl SearchEngine {
1919
pub fn new() -> Self {
20+
let mut config = Config::DEFAULT;
21+
config.normalize = true;
2022
Self {
21-
matcher: SkimMatcherV2::default().smart_case().use_cache(true),
23+
matcher: Matcher::new(config),
2224
}
2325
}
2426

@@ -37,15 +39,23 @@ impl SearchEngine {
3739

3840
let start_time = std::time::Instant::now();
3941

42+
// Create the search pattern with smart case matching
43+
let pattern = Atom::parse(query, CaseMatching::Smart, Normalization::Smart);
44+
45+
// Create a matcher instance for scoring
46+
let mut matcher = self.matcher.clone();
47+
4048
// First pass: collect fuzzy match results
4149
let mut results: Vec<SearchResult> = symbols
4250
.iter()
4351
.filter_map(|symbol| {
44-
self.matcher
45-
.fuzzy_match(&symbol.name, query)
52+
let mut buf = Vec::new();
53+
let haystack = Utf32Str::new(&symbol.name, &mut buf);
54+
pattern
55+
.score(haystack, &mut matcher)
4656
.map(|score| SearchResult {
4757
symbol: Arc::clone(symbol),
48-
score,
58+
score: score as i64,
4959
})
5060
})
5161
.collect();

0 commit comments

Comments
 (0)