Skip to content

Commit 675df33

Browse files
authored
Improve quality of 'workspace/symbols' response (#311)
The new method uses a fuzzy finder instead of the simple `String::starts_with(...)` method previously used.
1 parent 493c933 commit 675df33

File tree

4 files changed

+92
-20
lines changed

4 files changed

+92
-20
lines changed

Cargo.lock

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

vhdl_ls/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ log = "0"
2424
env_logger = "0"
2525
clap = { version = "4", features = ["derive"] }
2626
lsp-server = "0"
27+
fuzzy-matcher = "0.3.7"
2728

2829
[dev-dependencies]
2930
tempfile = "3"

vhdl_ls/src/vhdl_server.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use std::collections::hash_map::Entry;
1717
use vhdl_lang::ast::ObjectClass;
1818

1919
use crate::rpc_channel::SharedRpcChannel;
20+
use fuzzy_matcher::skim::SkimMatcherV2;
2021
use std::io;
2122
use std::io::ErrorKind;
2223
use std::path::{Path, PathBuf};
@@ -64,6 +65,7 @@ pub struct VHDLServer {
6465
init_params: Option<InitializeParams>,
6566
config_file: Option<PathBuf>,
6667
severity_map: SeverityMap,
68+
string_matcher: SkimMatcherV2,
6769
}
6870

6971
impl VHDLServer {
@@ -77,6 +79,7 @@ impl VHDLServer {
7779
init_params: None,
7880
config_file: None,
7981
severity_map: SeverityMap::default(),
82+
string_matcher: SkimMatcherV2::default().use_cache(true).ignore_case(),
8083
}
8184
}
8285

@@ -91,6 +94,7 @@ impl VHDLServer {
9194
init_params: None,
9295
config_file: None,
9396
severity_map: SeverityMap::default(),
97+
string_matcher: SkimMatcherV2::default(),
9498
}
9599
}
96100

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use crate::vhdl_server::{srcpos_to_location, to_symbol_kind, uri_to_file_name, VHDLServer};
2+
use fuzzy_matcher::FuzzyMatcher;
23
use lsp_types::{
34
DidChangeWatchedFilesParams, OneOf, WorkspaceSymbol, WorkspaceSymbolParams,
45
WorkspaceSymbolResponse,
56
};
7+
use std::cmp::Ordering;
8+
use std::collections::BinaryHeap;
69
use vhdl_lang::ast::Designator;
7-
use vhdl_lang::Message;
10+
use vhdl_lang::{EntRef, Message};
811

912
impl VHDLServer {
1013
pub fn workspace_did_change_watched_files(&mut self, params: &DidChangeWatchedFilesParams) {
@@ -32,39 +35,77 @@ impl VHDLServer {
3235
params: &WorkspaceSymbolParams,
3336
) -> Option<WorkspaceSymbolResponse> {
3437
let trunc_limit = 200;
35-
let query = params.query.to_ascii_lowercase();
36-
let mut symbols: Vec<_> = self
38+
let query = params.query.clone();
39+
let symbols = self
3740
.project
3841
.public_symbols()
3942
.filter_map(|ent| match ent.designator() {
4043
Designator::Identifier(_) | Designator::Character(_) => {
41-
Some((ent, ent.designator().to_string().to_ascii_lowercase()))
44+
Some((ent, ent.designator().to_string()))
4245
}
43-
Designator::OperatorSymbol(op) => Some((ent, op.to_string().to_ascii_lowercase())),
46+
Designator::OperatorSymbol(op) => Some((ent, op.to_string())),
4447
Designator::Anonymous(_) => None,
45-
})
46-
.collect();
47-
symbols.sort_by(|(_, n1), (_, n2)| n1.cmp(n2));
48+
});
49+
4850
Some(WorkspaceSymbolResponse::Nested(
49-
symbols
50-
.into_iter()
51-
.filter_map(|(ent, name)| {
52-
let decl_pos = ent.decl_pos()?;
53-
if name.starts_with(&query) {
54-
Some(WorkspaceSymbol {
51+
self.filter_workspace_symbols(symbols.into_iter(), &query, trunc_limit),
52+
))
53+
}
54+
55+
/// Filters found workspace symbols according to a given query.
56+
/// This uses a fuzzy matcher internally to improve the results.
57+
/// Queries 'close' to the target string will score high and be included in the
58+
/// returned vec, while queries 'not close' to the target string will be omitted.
59+
/// The returned vec is sorted according to the score of the fuzzy matcher.
60+
fn filter_workspace_symbols<'a>(
61+
&self,
62+
symbols: impl Iterator<Item = (EntRef<'a>, String)>,
63+
query: &str,
64+
trunc_limit: usize,
65+
) -> Vec<WorkspaceSymbol> {
66+
#[derive(Eq, PartialEq)]
67+
struct WorkspaceSymbolWithScore {
68+
symbol: WorkspaceSymbol,
69+
score: i64,
70+
}
71+
72+
impl PartialOrd<Self> for WorkspaceSymbolWithScore {
73+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
74+
Some(self.cmp(other))
75+
}
76+
}
77+
78+
impl Ord for WorkspaceSymbolWithScore {
79+
fn cmp(&self, other: &Self) -> Ordering {
80+
self.score.cmp(&other.score)
81+
}
82+
}
83+
84+
let symbols_with_scores: BinaryHeap<_> = symbols
85+
.into_iter()
86+
.filter_map(|(ent, name)| {
87+
let decl_pos = ent.decl_pos()?;
88+
self.string_matcher.fuzzy_match(&name, query).map(|score| {
89+
WorkspaceSymbolWithScore {
90+
symbol: WorkspaceSymbol {
5591
name: ent.describe(),
5692
kind: to_symbol_kind(ent.kind()),
5793
tags: None,
5894
container_name: ent.parent.map(|ent| ent.path_name()),
5995
location: OneOf::Left(srcpos_to_location(decl_pos)),
6096
data: None,
61-
})
62-
} else {
63-
None
97+
},
98+
score,
6499
}
65100
})
66-
.take(trunc_limit)
67-
.collect(),
68-
))
101+
})
102+
.take(trunc_limit)
103+
.collect();
104+
symbols_with_scores
105+
.into_sorted_vec()
106+
.into_iter()
107+
.rev()
108+
.map(|wsws| wsws.symbol)
109+
.collect()
69110
}
70111
}

0 commit comments

Comments
 (0)