]> git.lizzy.rs Git - rust.git/commitdiff
Implement a simple working assist
authorIgor Matuszewski <Xanewok@gmail.com>
Thu, 7 Mar 2019 00:48:31 +0000 (01:48 +0100)
committerIgor Matuszewski <Xanewok@gmail.com>
Sat, 16 Mar 2019 21:41:13 +0000 (22:41 +0100)
crates/ra_assists/src/add_missing_impl_members.rs

index 120109d4b7ca522d406276784207a7e0910a3c5c..4926a9b24c79103108ee7f51ff2f7b87d1fefde3 100644 (file)
@@ -1,12 +1,15 @@
 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`.
@@ -24,7 +27,21 @@ pub(crate) fn resolve_target_trait_def(
     }
 }
 
-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();
@@ -35,6 +52,7 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti
     }
 
     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;
@@ -47,23 +65,49 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti
     };
 
     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)]
@@ -78,24 +122,26 @@ fn test_add_missing_impl_members() {
             "
 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!() }<|>
 }",
         );
     }