From 6a0dc15bbba562d828042b36bf92018729a88314 Mon Sep 17 00:00:00 2001 From: Lukas Scheller Date: Sun, 16 Jun 2024 09:26:02 +0200 Subject: [PATCH 1/4] Use fuzzy matcher instead of 'starts_with' --- Cargo.lock | 26 ++++++++++++ vhdl_ls/Cargo.toml | 1 + vhdl_ls/src/vhdl_server.rs | 4 ++ vhdl_ls/src/vhdl_server/workspace.rs | 60 ++++++++++++++++++++-------- 4 files changed, 75 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42e8d9c8..4ceebc44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,6 +352,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -518,6 +527,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "option-ext" version = "0.2.0" @@ -870,6 +885,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1006,6 +1031,7 @@ dependencies = [ "clap", "env_logger", "fnv", + "fuzzy-matcher", "log", "lsp-server", "lsp-types", diff --git a/vhdl_ls/Cargo.toml b/vhdl_ls/Cargo.toml index 9b1967b3..a464e5da 100644 --- a/vhdl_ls/Cargo.toml +++ b/vhdl_ls/Cargo.toml @@ -24,6 +24,7 @@ log = "0" env_logger = "0" clap = { version = "4", features = ["derive"] } lsp-server = "0" +fuzzy-matcher = "0.3.7" [dev-dependencies] tempfile = "3" diff --git a/vhdl_ls/src/vhdl_server.rs b/vhdl_ls/src/vhdl_server.rs index f8922f12..c52c6acb 100644 --- a/vhdl_ls/src/vhdl_server.rs +++ b/vhdl_ls/src/vhdl_server.rs @@ -17,6 +17,7 @@ use std::collections::hash_map::Entry; use vhdl_lang::ast::ObjectClass; use crate::rpc_channel::SharedRpcChannel; +use fuzzy_matcher::skim::SkimMatcherV2; use std::io; use std::io::ErrorKind; use std::path::{Path, PathBuf}; @@ -64,6 +65,7 @@ pub struct VHDLServer { init_params: Option, config_file: Option, severity_map: SeverityMap, + string_matcher: SkimMatcherV2, } impl VHDLServer { @@ -77,6 +79,7 @@ impl VHDLServer { init_params: None, config_file: None, severity_map: SeverityMap::default(), + string_matcher: SkimMatcherV2::default().use_cache(true), } } @@ -91,6 +94,7 @@ impl VHDLServer { init_params: None, config_file: None, severity_map: SeverityMap::default(), + string_matcher: SkimMatcherV2::default(), } } diff --git a/vhdl_ls/src/vhdl_server/workspace.rs b/vhdl_ls/src/vhdl_server/workspace.rs index 88989cec..e58cfbc6 100644 --- a/vhdl_ls/src/vhdl_server/workspace.rs +++ b/vhdl_ls/src/vhdl_server/workspace.rs @@ -1,8 +1,11 @@ use crate::vhdl_server::{srcpos_to_location, to_symbol_kind, uri_to_file_name, VHDLServer}; +use fuzzy_matcher::FuzzyMatcher; use lsp_types::{ DidChangeWatchedFilesParams, OneOf, WorkspaceSymbol, WorkspaceSymbolParams, WorkspaceSymbolResponse, }; +use std::cmp::Ordering; +use std::collections::BinaryHeap; use vhdl_lang::ast::Designator; use vhdl_lang::Message; @@ -32,38 +35,63 @@ impl VHDLServer { params: &WorkspaceSymbolParams, ) -> Option { let trunc_limit = 200; - let query = params.query.to_ascii_lowercase(); - let mut symbols: Vec<_> = self + let query = params.query.clone(); + let symbols: Vec<_> = self .project .public_symbols() .filter_map(|ent| match ent.designator() { Designator::Identifier(_) | Designator::Character(_) => { - Some((ent, ent.designator().to_string().to_ascii_lowercase())) + Some((ent, ent.designator().to_string())) } - Designator::OperatorSymbol(op) => Some((ent, op.to_string().to_ascii_lowercase())), + Designator::OperatorSymbol(op) => Some((ent, op.to_string())), Designator::Anonymous(_) => None, }) .collect(); - symbols.sort_by(|(_, n1), (_, n2)| n1.cmp(n2)); - Some(WorkspaceSymbolResponse::Nested( - symbols - .into_iter() - .filter_map(|(ent, name)| { - let decl_pos = ent.decl_pos()?; - if name.starts_with(&query) { - Some(WorkspaceSymbol { + + #[derive(Eq, PartialEq)] + struct WorkspaceSymbolWithScore { + symbol: WorkspaceSymbol, + score: i64, + } + + impl PartialOrd for WorkspaceSymbolWithScore { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for WorkspaceSymbolWithScore { + fn cmp(&self, other: &Self) -> Ordering { + self.score.cmp(&other.score) + } + } + + let symbols_with_scores: BinaryHeap<_> = symbols + .into_iter() + .filter_map(|(ent, name)| { + let decl_pos = ent.decl_pos()?; + self.string_matcher.fuzzy_match(&name, &query).map(|score| { + WorkspaceSymbolWithScore { + symbol: WorkspaceSymbol { name: ent.describe(), kind: to_symbol_kind(ent.kind()), tags: None, container_name: ent.parent.map(|ent| ent.path_name()), location: OneOf::Left(srcpos_to_location(decl_pos)), data: None, - }) - } else { - None + }, + score, } }) - .take(trunc_limit) + }) + .take(trunc_limit) + .collect(); + Some(WorkspaceSymbolResponse::Nested( + symbols_with_scores + .into_sorted_vec() + .into_iter() + .rev() + .map(|wsws| wsws.symbol) .collect(), )) } From 46f3f5684e499ea0da22feca24aa8c4700439186 Mon Sep 17 00:00:00 2001 From: Lukas Scheller Date: Sun, 16 Jun 2024 09:58:43 +0200 Subject: [PATCH 2/4] Refactor into custom function --- vhdl_ls/src/vhdl_server/workspace.rs | 36 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/vhdl_ls/src/vhdl_server/workspace.rs b/vhdl_ls/src/vhdl_server/workspace.rs index e58cfbc6..7352801e 100644 --- a/vhdl_ls/src/vhdl_server/workspace.rs +++ b/vhdl_ls/src/vhdl_server/workspace.rs @@ -7,7 +7,7 @@ use lsp_types::{ use std::cmp::Ordering; use std::collections::BinaryHeap; use vhdl_lang::ast::Designator; -use vhdl_lang::Message; +use vhdl_lang::{EntRef, Message}; impl VHDLServer { pub fn workspace_did_change_watched_files(&mut self, params: &DidChangeWatchedFilesParams) { @@ -36,7 +36,7 @@ impl VHDLServer { ) -> Option { let trunc_limit = 200; let query = params.query.clone(); - let symbols: Vec<_> = self + let symbols = self .project .public_symbols() .filter_map(|ent| match ent.designator() { @@ -45,9 +45,21 @@ impl VHDLServer { } Designator::OperatorSymbol(op) => Some((ent, op.to_string())), Designator::Anonymous(_) => None, - }) - .collect(); + }); + + Some(WorkspaceSymbolResponse::Nested(self.filter_map_symbols( + symbols.into_iter(), + &query, + trunc_limit, + ))) + } + fn filter_map_symbols<'a>( + &self, + symbols: impl Iterator, String)>, + query: &str, + trunc_limit: usize, + ) -> Vec { #[derive(Eq, PartialEq)] struct WorkspaceSymbolWithScore { symbol: WorkspaceSymbol, @@ -70,7 +82,7 @@ impl VHDLServer { .into_iter() .filter_map(|(ent, name)| { let decl_pos = ent.decl_pos()?; - self.string_matcher.fuzzy_match(&name, &query).map(|score| { + self.string_matcher.fuzzy_match(&name, query).map(|score| { WorkspaceSymbolWithScore { symbol: WorkspaceSymbol { name: ent.describe(), @@ -86,13 +98,11 @@ impl VHDLServer { }) .take(trunc_limit) .collect(); - Some(WorkspaceSymbolResponse::Nested( - symbols_with_scores - .into_sorted_vec() - .into_iter() - .rev() - .map(|wsws| wsws.symbol) - .collect(), - )) + symbols_with_scores + .into_sorted_vec() + .into_iter() + .rev() + .map(|wsws| wsws.symbol) + .collect() } } From ee83f05adf6e9e4f667abc36c5180dd24f841b15 Mon Sep 17 00:00:00 2001 From: Lukas Scheller Date: Sun, 16 Jun 2024 10:06:18 +0200 Subject: [PATCH 3/4] Ignore casing for matcher --- vhdl_ls/src/vhdl_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vhdl_ls/src/vhdl_server.rs b/vhdl_ls/src/vhdl_server.rs index c52c6acb..6b6655bc 100644 --- a/vhdl_ls/src/vhdl_server.rs +++ b/vhdl_ls/src/vhdl_server.rs @@ -79,7 +79,7 @@ impl VHDLServer { init_params: None, config_file: None, severity_map: SeverityMap::default(), - string_matcher: SkimMatcherV2::default().use_cache(true), + string_matcher: SkimMatcherV2::default().use_cache(true).ignore_case(), } } From 1b616eb1142d79716286f4488adb3cc0831e57da Mon Sep 17 00:00:00 2001 From: Lukas Scheller Date: Sun, 16 Jun 2024 10:08:35 +0200 Subject: [PATCH 4/4] Add documentation --- vhdl_ls/src/vhdl_server/workspace.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vhdl_ls/src/vhdl_server/workspace.rs b/vhdl_ls/src/vhdl_server/workspace.rs index 7352801e..b17da42e 100644 --- a/vhdl_ls/src/vhdl_server/workspace.rs +++ b/vhdl_ls/src/vhdl_server/workspace.rs @@ -47,14 +47,17 @@ impl VHDLServer { Designator::Anonymous(_) => None, }); - Some(WorkspaceSymbolResponse::Nested(self.filter_map_symbols( - symbols.into_iter(), - &query, - trunc_limit, - ))) + Some(WorkspaceSymbolResponse::Nested( + self.filter_workspace_symbols(symbols.into_iter(), &query, trunc_limit), + )) } - fn filter_map_symbols<'a>( + /// Filters found workspace symbols according to a given query. + /// This uses a fuzzy matcher internally to improve the results. + /// Queries 'close' to the target string will score high and be included in the + /// returned vec, while queries 'not close' to the target string will be omitted. + /// The returned vec is sorted according to the score of the fuzzy matcher. + fn filter_workspace_symbols<'a>( &self, symbols: impl Iterator, String)>, query: &str,