blob: aab62c1d1ebb06a3d3a44eceec9d29616146f788 [file] [log] [blame]
use crate::ast::with_error_checking_parse;
use crate::core::{Match, Session};
use crate::typeinf::get_function_declaration;
use syntax::ast::ImplItemKind;
/// Returns completion snippets usable by some editors
///
/// Generates a snippet string given a `Match`. The provided snippet contains
/// substrings like "${1:name}" which some editors can use to quickly fill in
/// arguments.
///
/// # Examples
///
/// ```no_run
/// extern crate racer;
///
/// use std::path::Path;
///
/// let path = Path::new(".");
/// let cache = racer::FileCache::default();
/// let session = racer::Session::new(&cache, Some(path));
///
/// let m = racer::complete_fully_qualified_name(
/// "std::fs::canonicalize",
/// &path,
/// &session
/// ).next().unwrap();
///
/// let snip = racer::snippet_for_match(&m, &session);
/// assert_eq!(snip, "canonicalize(${1:path})");
/// ```
pub fn snippet_for_match(m: &Match, session: &Session<'_>) -> String {
if m.mtype.is_function() {
let method = get_function_declaration(m, session);
if let Some(m) = MethodInfo::from_source_str(&method) {
m.snippet()
} else {
"".into()
}
} else {
m.matchstr.clone()
}
}
struct MethodInfo {
name: String,
args: Vec<String>,
}
impl MethodInfo {
///Parses method declaration as string and returns relevant data
fn from_source_str(source: &str) -> Option<MethodInfo> {
let trim: &[_] = &['\n', '\r', '{', ' '];
let decorated = format!("{} {{}}()", source.trim_end_matches(trim));
trace!("MethodInfo::from_source_str: {:?}", decorated);
with_error_checking_parse(decorated, |p| {
let mut at_end = false;
if let Ok(method) = p.parse_impl_item(&mut at_end) {
if let ImplItemKind::Method(ref msig, _) = method.kind {
let decl = &msig.decl;
return Some(MethodInfo {
// ident.as_str calls Ident.name.as_str
name: method.ident.name.to_string(),
args: decl
.inputs
.iter()
.map(|arg| {
let source_map = &p.sess.source_map();
let var_name = match source_map.span_to_snippet(arg.pat.span) {
Ok(name) => name,
_ => "".into(),
};
match source_map.span_to_snippet(arg.ty.span) {
Ok(ref type_name) if !type_name.is_empty() => {
format!("{}: {}", var_name, type_name)
}
_ => var_name,
}
})
.collect(),
});
}
}
debug!("Unable to parse method declaration. |{}|", source);
None
})
}
///Returns completion snippets usable by some editors
fn snippet(&self) -> String {
format!(
"{}({})",
self.name,
&self
.args
.iter()
.filter(|&s| !s.ends_with("self"))
.enumerate()
.fold(String::new(), |cur, (i, ref s)| {
let arg = format!("${{{}:{}}}", i + 1, s);
let delim = if i > 0 { ", " } else { "" };
cur + delim + &arg
})
)
}
}
#[test]
fn method_info_test() {
let info = MethodInfo::from_source_str("pub fn new() -> Vec<T>").unwrap();
assert_eq!(info.name, "new");
assert_eq!(info.args.len(), 0);
assert_eq!(info.snippet(), "new()");
let info = MethodInfo::from_source_str("pub fn reserve(&mut self, additional: usize)").unwrap();
assert_eq!(info.name, "reserve");
assert_eq!(info.args.len(), 2);
// it looks odd, but no problme because what our clients see is only snippet
assert_eq!(info.args[0], "&mut self: &mut self");
assert_eq!(info.snippet(), "reserve(${1:additional: usize})");
}