Skip to content

Commit

Permalink
Improve quality of 'workspace/symbols' response (#311)
Browse files Browse the repository at this point in the history
The new method uses a fuzzy finder instead of the simple `String::starts_with(...)` method previously used.
  • Loading branch information
Schottkyc137 authored Jun 16, 2024
1 parent 493c933 commit 675df33
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 20 deletions.
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vhdl_ls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions vhdl_ls/src/vhdl_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -64,6 +65,7 @@ pub struct VHDLServer {
init_params: Option<InitializeParams>,
config_file: Option<PathBuf>,
severity_map: SeverityMap,
string_matcher: SkimMatcherV2,
}

impl VHDLServer {
Expand All @@ -77,6 +79,7 @@ impl VHDLServer {
init_params: None,
config_file: None,
severity_map: SeverityMap::default(),
string_matcher: SkimMatcherV2::default().use_cache(true).ignore_case(),
}
}

Expand All @@ -91,6 +94,7 @@ impl VHDLServer {
init_params: None,
config_file: None,
severity_map: SeverityMap::default(),
string_matcher: SkimMatcherV2::default(),
}
}

Expand Down
81 changes: 61 additions & 20 deletions vhdl_ls/src/vhdl_server/workspace.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
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;
use vhdl_lang::{EntRef, Message};

impl VHDLServer {
pub fn workspace_did_change_watched_files(&mut self, params: &DidChangeWatchedFilesParams) {
Expand Down Expand Up @@ -32,39 +35,77 @@ impl VHDLServer {
params: &WorkspaceSymbolParams,
) -> Option<WorkspaceSymbolResponse> {
let trunc_limit = 200;
let query = params.query.to_ascii_lowercase();
let mut symbols: Vec<_> = self
let query = params.query.clone();
let symbols = 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 {
self.filter_workspace_symbols(symbols.into_iter(), &query, trunc_limit),
))
}

/// 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<Item = (EntRef<'a>, String)>,
query: &str,
trunc_limit: usize,
) -> Vec<WorkspaceSymbol> {
#[derive(Eq, PartialEq)]
struct WorkspaceSymbolWithScore {
symbol: WorkspaceSymbol,
score: i64,
}

impl PartialOrd<Self> for WorkspaceSymbolWithScore {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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)
.collect(),
))
})
.take(trunc_limit)
.collect();
symbols_with_scores
.into_sorted_vec()
.into_iter()
.rev()
.map(|wsws| wsws.symbol)
.collect()
}
}

0 comments on commit 675df33

Please sign in to comment.