mod analyzer;
mod lsp;
mod probe;
use std::process;
use dialoguer::{theme::ColorfulTheme, FuzzySelect};
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
fn usage(bin: &str) {
eprintln!("Usage: {bin} <type> [filter|-i]");
eprintln!();
eprintln!("Examples:");
eprintln!(" {bin} u8");
eprintln!(" {bin} String");
eprintln!(" {bin} \"Vec<i32>\"");
eprintln!(" {bin} \"HashMap<String,u32>\"");
eprintln!(" {bin} u8 wrapping # fuzzy filter");
eprintln!(" {bin} u8 -i # interactive picker");
}
fn main() {
let bin = std::env::current_exe()
.ok()
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
.unwrap_or_else(|| "rust-meth".to_string());
let args: Vec<String> = std::env::args().skip(1).collect();
if args.is_empty() || args[0] == "--help" || args[0] == "-h" {
usage(&bin);
process::exit(0);
}
let type_name = &args[0];
let interactive = args.iter().any(|a| a == "-i" || a == "--interactive");
let filter = if interactive {
None
} else {
args.get(1).map(String::as_str)
};
let ra_path = match analyzer::find_rust_analyzer() {
Ok(p) => p,
Err(e) => {
eprintln!("error: {e}");
process::exit(1);
}
};
let methods = match analyzer::query_methods(type_name, &ra_path) {
Ok(m) => m,
Err(e) => {
eprintln!("error: {e}");
process::exit(1);
}
};
if interactive {
let items: Vec<&str> = methods.iter().map(|m| m.name.as_str()).collect();
let selection = FuzzySelect::with_theme(&ColorfulTheme::default())
.with_prompt(format!("Methods on `{type_name}`"))
.items(&items)
.interact_opt();
match selection {
Ok(Some(idx)) => {
let m = &methods[idx];
match &m.detail {
Some(detail) => println!(" {} {detail}", m.name),
None => println!(" {}", m.name),
}
}
Ok(None) => {} Err(e) => {
eprintln!("error: {e}");
process::exit(1);
}
}
return;
}
let matched: Vec<_> = filter.map_or_else(
|| methods.iter().collect(),
|pat| {
let matcher = SkimMatcherV2::default();
let mut scored: Vec<_> = methods
.iter()
.filter_map(|m| matcher.fuzzy_match(&m.name, pat).map(|score| (score, m)))
.collect();
scored.sort_by_key(|(score, _)| std::cmp::Reverse(*score));
scored.into_iter().map(|(_, m)| m).collect()
},
);
if matched.is_empty() {
match filter {
Some(pat) => eprintln!("No methods on `{type_name}` matching {pat:?}"),
None => eprintln!("No methods found for type `{type_name}`"),
}
process::exit(1);
}
match filter {
Some(pat) => println!("{bin}: methods on `{type_name}` matching {pat:?}\n"),
None => println!("{bin}: methods on `{type_name}`\n"),
}
let name_width = matched.iter().map(|m| m.name.len()).max().unwrap_or(0);
for m in &matched {
match &m.detail {
Some(detail) => println!(" {:<name_width$} {detail}", m.name),
None => println!(" {}", m.name),
}
}
println!("\n{} method(s)", matched.len());
}