Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve quality of workspace symbols #311

Merged
merged 4 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
}
Loading