use std::collections::HashSet;
-use crate::assist_ctx::{Assist, AssistCtx};
+use crate::{Assist, AssistId, AssistCtx};
use hir::Resolver;
use hir::db::HirDatabase;
-use ra_syntax::{SmolStr, SyntaxKind, SyntaxNode, TreeArc};
+use ra_syntax::{SmolStr, SyntaxKind, SyntaxNode, TextUnit, TreeArc};
use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner};
use ra_db::FilePosition;
+use ra_fmt::{leading_indent, reindent};
+
+use itertools::Itertools;
/// Given an `ast::ImplBlock`, resolves the target trait (the one being
/// implemented) to a `ast::TraitDef`.
}
}
-pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn build_func_body(def: &ast::FnDef) -> String {
+ let mut buf = String::new();
+
+ for child in def.syntax().children() {
+ if child.kind() == SyntaxKind::SEMI {
+ buf.push_str(" { unimplemented!() }")
+ } else {
+ child.text().push_to(&mut buf);
+ }
+ }
+
+ buf.trim_end().to_string()
+}
+
+pub(crate) fn add_missing_impl_members(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
use SyntaxKind::{IMPL_BLOCK, ITEM_LIST, WHITESPACE};
let node = ctx.covering_node();
}
let impl_node = node.ancestors().find_map(ast::ImplBlock::cast)?;
+ let impl_item_list = impl_node.item_list()?;
let trait_def = {
let db = ctx.db;
};
let fn_def_opt = |kind| if let ImplItemKind::FnDef(def) = kind { Some(def) } else { None };
- let def_name = |&def| -> Option<&SmolStr> { FnDef::name(def).map(ast::Name::text) };
+ let def_name = |def| -> Option<&SmolStr> { FnDef::name(def).map(ast::Name::text) };
let trait_items = trait_def.syntax().descendants().find_map(ast::ItemList::cast)?.impl_items();
- let impl_items = impl_node.item_list()?.impl_items();
+ let impl_items = impl_item_list.impl_items();
let trait_fns = trait_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>();
let impl_fns = impl_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>();
- let trait_fn_names = trait_fns.iter().filter_map(def_name).collect::<HashSet<_>>();
- let impl_fn_names = impl_fns.iter().filter_map(def_name).collect::<HashSet<_>>();
+ let trait_fn_names = trait_fns.iter().cloned().filter_map(def_name).collect::<HashSet<_>>();
+ let impl_fn_names = impl_fns.iter().cloned().filter_map(def_name).collect::<HashSet<_>>();
let missing_fn_names = trait_fn_names.difference(&impl_fn_names).collect::<HashSet<_>>();
- let missing_fns = trait_fns
+ let missing_fns: Vec<_> = trait_fns
.iter()
- .filter(|&t| def_name(t).map(|n| missing_fn_names.contains(&n)).unwrap_or(false));
+ .cloned()
+ .filter(|t| def_name(t).map(|n| missing_fn_names.contains(&n)).unwrap_or(false))
+ .collect();
- unimplemented!()
+ if missing_fns.is_empty() {
+ return None;
+ }
+
+ let last_whitespace_node =
+ impl_item_list.syntax().children().filter_map(ast::Whitespace::cast).last()?.syntax();
+
+ ctx.add_action(AssistId("add_impl_missing_members"), "add impl missing members", |edit| {
+ let func_bodies = missing_fns.into_iter().map(build_func_body).join("\n");
+ let func_bodies = String::from("\n") + &func_bodies;
+
+ let first_impl_item = impl_item_list.impl_items().next();
+ // FIXME: We should respect the indent of the first item from the item list or the indent of leading block + some default indent (4?)
+ // Another approach is to not indent at all if there are no items here
+ let indent = first_impl_item.and_then(|i| leading_indent(i.syntax())).unwrap_or_default();
+ let func_bodies = reindent(&func_bodies, indent) + "\n";
+
+ let changed_range = last_whitespace_node.range();
+ let replaced_text_range = TextUnit::of_str(&func_bodies);
+
+ edit.replace(changed_range, func_bodies);
+ edit.set_cursor(changed_range.start() + replaced_text_range - TextUnit::of_str("\n"));
+ });
+
+ ctx.build()
}
#[cfg(test)]
"
trait Foo {
fn foo(&self);
+ fn bar(&self);
}
struct S;
impl Foo for S {
+ fn bar(&self) {}
<|>
}",
"
trait Foo {
fn foo(&self);
+ fn bar(&self);
}
struct S;
impl Foo for S {
- fn foo(&self) {
- <|>
- }
+ fn bar(&self) {}
+ fn foo(&self) { unimplemented!() }<|>
}",
);
}