From 7cc845e88d870173e1baa39ce4d3885a5b1f7043 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 21 Apr 2019 17:47:55 +0300 Subject: start structured editing API --- crates/ra_assists/src/ast_editor.rs | 153 ++++++++++++++++++++++++++++++++++++ crates/ra_assists/src/lib.rs | 1 + 2 files changed, 154 insertions(+) create mode 100644 crates/ra_assists/src/ast_editor.rs (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs new file mode 100644 index 000000000..7347c1738 --- /dev/null +++ b/crates/ra_assists/src/ast_editor.rs @@ -0,0 +1,153 @@ +use arrayvec::ArrayVec; +use ra_text_edit::{TextEdit, TextEditBuilder}; +use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; + +pub struct AstEditor { + original_ast: TreeArc, + ast: TreeArc, +} + +impl AstEditor { + pub fn new(node: &N) -> AstEditor { + AstEditor { original_ast: node.to_owned(), ast: node.to_owned() } + } + + pub fn into_text_edit(self) -> TextEdit { + // FIXME: compute a more fine-grained diff here. + // If *you* know a nice algorithm to compute diff between two syntax + // tree, tell me about it! + let mut builder = TextEditBuilder::default(); + builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string()); + builder.finish() + } + + pub fn ast(&self) -> &N { + &*self.ast + } +} + +impl AstEditor { + pub fn append_field(&mut self, field: &ast::NamedField) { + self.insert_field(InsertPosition::Last, field) + } + + pub fn insert_field( + &mut self, + position: InsertPosition<&'_ ast::NamedField>, + field: &ast::NamedField, + ) { + let mut to_insert: ArrayVec<[SyntaxElement; 2]> = + [field.syntax().into(), tokens::comma().into()].into(); + let position = match position { + InsertPosition::First => { + let anchor = match self + .ast() + .syntax() + .children_with_tokens() + .find(|it| it.kind() == L_CURLY) + { + Some(it) => it, + None => return, + }; + InsertPosition::After(anchor) + } + InsertPosition::Last => { + let anchor = match self + .ast() + .syntax() + .children_with_tokens() + .find(|it| it.kind() == R_CURLY) + { + Some(it) => it, + None => return, + }; + InsertPosition::Before(anchor) + } + InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()), + InsertPosition::After(anchor) => { + if let Some(comma) = anchor + .syntax() + .siblings_with_tokens(Direction::Next) + .find(|it| it.kind() == COMMA) + { + InsertPosition::After(comma) + } else { + to_insert.insert(0, tokens::comma().into()); + InsertPosition::After(anchor.syntax().into()) + } + } + }; + self.ast = insert_children_into_ast(self.ast(), position, to_insert.iter().cloned()); + } +} + +fn insert_children_into_ast<'a, N: AstNode>( + node: &N, + position: InsertPosition>, + to_insert: impl Iterator>, +) -> TreeArc { + let new_syntax = node.syntax().insert_children(position, to_insert); + N::cast(&new_syntax).unwrap().to_owned() +} + +pub struct AstBuilder { + _phantom: std::marker::PhantomData, +} + +impl AstBuilder { + pub fn from_text(text: &str) -> TreeArc { + ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text)) + } +} + +fn ast_node_from_file_text(text: &str) -> TreeArc { + let file = SourceFile::parse(text); + let res = file.syntax().descendants().find_map(N::cast).unwrap().to_owned(); + res +} + +mod tokens { + use lazy_static::lazy_static; + use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*}; + + lazy_static! { + static ref SOURCE_FILE: TreeArc = SourceFile::parse(","); + } + + pub(crate) fn comma() -> SyntaxToken<'static> { + SOURCE_FILE + .syntax() + .descendants_with_tokens() + .filter_map(|it| it.as_token()) + .find(|it| it.kind() == COMMA) + .unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use ra_syntax::SourceFile; + + #[test] + fn structure_editing() { + let file = SourceFile::parse( + "\ +fn foo() { + let s = S { + original: 92, + } +} +", + ); + let field_list = file.syntax().descendants().find_map(ast::NamedFieldList::cast).unwrap(); + let mut editor = AstEditor::new(field_list); + + let field = AstBuilder::::from_text("first_inserted: 1"); + editor.append_field(&field); + let field = AstBuilder::::from_text("second_inserted: 2"); + editor.append_field(&field); + eprintln!("{}", editor.ast().syntax()); + } +} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index ded401b63..3151b1c44 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -7,6 +7,7 @@ mod assist_ctx; mod marks; +pub mod ast_editor; use itertools::Itertools; -- cgit v1.2.3 From f313ac45af0b9c81cf411a8a4a6873e742bb8562 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 21 Apr 2019 22:13:52 +0300 Subject: use structured editing API for fill struct assist --- crates/ra_assists/src/assist_ctx.rs | 4 + crates/ra_assists/src/ast_editor.rs | 140 +++++++++++++++++++--------- crates/ra_assists/src/fill_struct_fields.rs | 113 +++++++--------------- 3 files changed, 138 insertions(+), 119 deletions(-) (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index e80e35738..f46de61eb 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs @@ -161,6 +161,10 @@ impl AssistBuilder { self.target = Some(target) } + pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { + &mut self.edit + } + fn build(self) -> AssistAction { AssistAction { edit: self.edit.finish(), diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 7347c1738..5bf1fc0ed 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs @@ -1,6 +1,9 @@ +use std::iter; + use arrayvec::ArrayVec; -use ra_text_edit::{TextEdit, TextEditBuilder}; +use ra_text_edit::TextEditBuilder; use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; +use ra_fmt::leading_indent; pub struct AstEditor { original_ast: TreeArc, @@ -12,18 +15,25 @@ impl AstEditor { AstEditor { original_ast: node.to_owned(), ast: node.to_owned() } } - pub fn into_text_edit(self) -> TextEdit { + pub fn into_text_edit(self, builder: &mut TextEditBuilder) { // FIXME: compute a more fine-grained diff here. // If *you* know a nice algorithm to compute diff between two syntax // tree, tell me about it! - let mut builder = TextEditBuilder::default(); builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string()); - builder.finish() } pub fn ast(&self) -> &N { &*self.ast } + + fn insert_children<'a>( + &self, + position: InsertPosition>, + to_insert: impl Iterator>, + ) -> TreeArc { + let new_syntax = self.ast().syntax().insert_children(position, to_insert); + N::cast(&new_syntax).unwrap().to_owned() + } } impl AstEditor { @@ -31,41 +41,61 @@ impl AstEditor { self.insert_field(InsertPosition::Last, field) } + pub fn make_multiline(&mut self) { + let l_curly = match self.l_curly() { + Some(it) => it, + None => return, + }; + let sibling = match l_curly.next_sibling_or_token() { + Some(it) => it, + None => return, + }; + if sibling.as_token().map(|it| it.text().contains('\n')) == Some(true) { + return; + } + + let ws = tokens::WsBuilder::new(&format!( + "\n{}", + leading_indent(self.ast().syntax()).unwrap_or("") + )); + self.ast = self.insert_children(InsertPosition::After(l_curly), iter::once(ws.ws().into())); + } + pub fn insert_field( &mut self, position: InsertPosition<&'_ ast::NamedField>, field: &ast::NamedField, ) { - let mut to_insert: ArrayVec<[SyntaxElement; 2]> = - [field.syntax().into(), tokens::comma().into()].into(); - let position = match position { - InsertPosition::First => { - let anchor = match self - .ast() - .syntax() - .children_with_tokens() - .find(|it| it.kind() == L_CURLY) - { + let is_multiline = self.ast().syntax().text().contains('\n'); + let ws; + let space = if is_multiline { + ws = tokens::WsBuilder::new(&format!( + "\n{} ", + leading_indent(self.ast().syntax()).unwrap_or("") + )); + ws.ws() + } else { + tokens::single_space() + }; + + let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); + to_insert.push(space.into()); + to_insert.push(field.syntax().into()); + to_insert.push(tokens::comma().into()); + + macro_rules! after_l_curly { + () => {{ + let anchor = match self.l_curly() { Some(it) => it, None => return, }; InsertPosition::After(anchor) - } - InsertPosition::Last => { - let anchor = match self - .ast() - .syntax() - .children_with_tokens() - .find(|it| it.kind() == R_CURLY) - { - Some(it) => it, - None => return, - }; - InsertPosition::Before(anchor) - } - InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()), - InsertPosition::After(anchor) => { - if let Some(comma) = anchor + }}; + } + + macro_rules! after_field { + ($anchor:expr) => { + if let Some(comma) = $anchor .syntax() .siblings_with_tokens(Direction::Next) .find(|it| it.kind() == COMMA) @@ -73,21 +103,26 @@ impl AstEditor { InsertPosition::After(comma) } else { to_insert.insert(0, tokens::comma().into()); - InsertPosition::After(anchor.syntax().into()) + InsertPosition::After($anchor.syntax().into()) } - } + }; + }; + + let position = match position { + InsertPosition::First => after_l_curly!(), + InsertPosition::Last => match self.ast().fields().last() { + Some(it) => after_field!(it), + None => after_l_curly!(), + }, + InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()), + InsertPosition::After(anchor) => after_field!(anchor), }; - self.ast = insert_children_into_ast(self.ast(), position, to_insert.iter().cloned()); + self.ast = self.insert_children(position, to_insert.iter().cloned()); } -} -fn insert_children_into_ast<'a, N: AstNode>( - node: &N, - position: InsertPosition>, - to_insert: impl Iterator>, -) -> TreeArc { - let new_syntax = node.syntax().insert_children(position, to_insert); - N::cast(&new_syntax).unwrap().to_owned() + fn l_curly(&self) -> Option { + self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) + } } pub struct AstBuilder { @@ -111,7 +146,7 @@ mod tokens { use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*}; lazy_static! { - static ref SOURCE_FILE: TreeArc = SourceFile::parse(","); + static ref SOURCE_FILE: TreeArc = SourceFile::parse(",\n; ;"); } pub(crate) fn comma() -> SyntaxToken<'static> { @@ -122,6 +157,27 @@ mod tokens { .find(|it| it.kind() == COMMA) .unwrap() } + + pub(crate) fn single_space() -> SyntaxToken<'static> { + SOURCE_FILE + .syntax() + .descendants_with_tokens() + .filter_map(|it| it.as_token()) + .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ") + .unwrap() + } + + pub(crate) struct WsBuilder(TreeArc); + + impl WsBuilder { + pub(crate) fn new(text: &str) -> WsBuilder { + WsBuilder(SourceFile::parse(text)) + } + pub(crate) fn ws(&self) -> SyntaxToken<'_> { + self.0.syntax().first_child_or_token().unwrap().as_token().unwrap() + } + } + } #[cfg(test)] diff --git a/crates/ra_assists/src/fill_struct_fields.rs b/crates/ra_assists/src/fill_struct_fields.rs index 663b4f669..ca128168a 100644 --- a/crates/ra_assists/src/fill_struct_fields.rs +++ b/crates/ra_assists/src/fill_struct_fields.rs @@ -1,94 +1,53 @@ -use std::fmt::Write; - use hir::{AdtDef, db::HirDatabase}; use ra_syntax::ast::{self, AstNode}; -use crate::{AssistCtx, Assist, AssistId}; +use crate::{AssistCtx, Assist, AssistId, ast_editor::{AstEditor, AstBuilder}}; pub(crate) fn fill_struct_fields(mut ctx: AssistCtx) -> Option { let struct_lit = ctx.node_at_offset::()?; - let mut fsf = FillStructFields { - ctx: &mut ctx, - named_field_list: struct_lit.named_field_list()?, - struct_fields: vec![], - struct_lit, - }; - fsf.evaluate_struct_def_fields()?; - if fsf.struct_lit_and_def_have_the_same_number_of_fields() { - return None; - } - fsf.remove_already_included_fields()?; - fsf.add_action()?; - ctx.build() -} - -struct FillStructFields<'a, 'b: 'a, DB> { - ctx: &'a mut AssistCtx<'b, DB>, - named_field_list: &'a ast::NamedFieldList, - struct_fields: Vec<(String, String)>, - struct_lit: &'a ast::StructLit, -} + let named_field_list = struct_lit.named_field_list()?; -impl FillStructFields<'_, '_, DB> -where - DB: HirDatabase, -{ - fn add_action(&mut self) -> Option<()> { - let named_field_list = self.named_field_list; - let struct_fields_string = self.struct_fields_string()?; - let struct_lit = self.struct_lit; - self.ctx.add_action(AssistId("fill_struct_fields"), "fill struct fields", |edit| { - edit.target(struct_lit.syntax().range()); - edit.set_cursor(struct_lit.syntax().range().start()); - edit.replace_node_and_indent(named_field_list.syntax(), struct_fields_string); - }); - Some(()) - } - - fn struct_lit_and_def_have_the_same_number_of_fields(&self) -> bool { - self.named_field_list.fields().count() == self.struct_fields.len() - } - - fn evaluate_struct_def_fields(&mut self) -> Option<()> { - let analyzer = hir::SourceAnalyzer::new( - self.ctx.db, - self.ctx.frange.file_id, - self.struct_lit.syntax(), - None, - ); - let struct_lit_ty = analyzer.type_of(self.ctx.db, self.struct_lit.into())?; + // Collect all fields from struct definition + let mut fields = { + let analyzer = + hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, struct_lit.syntax(), None); + let struct_lit_ty = analyzer.type_of(ctx.db, struct_lit.into())?; let struct_def = match struct_lit_ty.as_adt() { Some((AdtDef::Struct(s), _)) => s, _ => return None, }; - self.struct_fields = struct_def - .fields(self.ctx.db) - .into_iter() - .map(|f| (f.name(self.ctx.db).to_string(), "()".into())) - .collect(); - Some(()) - } + struct_def.fields(ctx.db) + }; - fn remove_already_included_fields(&mut self) -> Option<()> { - for ast_field in self.named_field_list.fields() { - let expr = ast_field.expr()?.syntax().text().to_string(); - let name_from_ast = ast_field.name_ref()?.text().to_string(); - if let Some(idx) = self.struct_fields.iter().position(|(n, _)| n == &name_from_ast) { - self.struct_fields[idx] = (name_from_ast, expr); - } - } - Some(()) + // Filter out existing fields + for ast_field in named_field_list.fields() { + let name_from_ast = ast_field.name_ref()?.text().to_string(); + fields.retain(|field| field.name(ctx.db).to_string() != name_from_ast); + } + if fields.is_empty() { + return None; } - fn struct_fields_string(&mut self) -> Option { - let mut buf = String::from("{\n"); - for (name, expr) in &self.struct_fields { - write!(&mut buf, " {}: {},\n", name, expr).unwrap(); + let db = ctx.db; + ctx.add_action(AssistId("fill_struct_fields"), "fill struct fields", |edit| { + let mut ast_editor = AstEditor::new(named_field_list); + if named_field_list.fields().count() == 0 && fields.len() > 2 { + ast_editor.make_multiline(); + }; + + for field in fields { + let field = + AstBuilder::::from_text(&format!("{}: ()", field.name(db))); + ast_editor.append_field(&field); } - buf.push_str("}"); - Some(buf) - } + + edit.target(struct_lit.syntax().range()); + edit.set_cursor(struct_lit.syntax().range().start()); + + ast_editor.into_text_edit(edit.text_edit_builder()); + }); + ctx.build() } #[cfg(test)] @@ -225,11 +184,11 @@ mod tests { fn main() { let s = <|>S { + c: (1, 2), + e: "foo", a: (), b: (), - c: (1, 2), d: (), - e: "foo", } } "#, -- cgit v1.2.3 From 97f41d7343a4801f296cd6a7a45ece9249c15886 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 22 Apr 2019 00:52:21 +0300 Subject: test short structs --- crates/ra_assists/src/ast_editor.rs | 15 +++++++++++---- crates/ra_assists/src/fill_struct_fields.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 5bf1fc0ed..7ef58aa8e 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs @@ -110,13 +110,20 @@ impl AstEditor { let position = match position { InsertPosition::First => after_l_curly!(), - InsertPosition::Last => match self.ast().fields().last() { - Some(it) => after_field!(it), - None => after_l_curly!(), - }, + InsertPosition::Last => { + if !is_multiline { + // don't insert comma before curly + to_insert.pop(); + } + match self.ast().fields().last() { + Some(it) => after_field!(it), + None => after_l_curly!(), + } + } InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()), InsertPosition::After(anchor) => after_field!(anchor), }; + self.ast = self.insert_children(position, to_insert.iter().cloned()); } diff --git a/crates/ra_assists/src/fill_struct_fields.rs b/crates/ra_assists/src/fill_struct_fields.rs index ca128168a..302d62ab1 100644 --- a/crates/ra_assists/src/fill_struct_fields.rs +++ b/crates/ra_assists/src/fill_struct_fields.rs @@ -194,4 +194,31 @@ mod tests { "#, ); } + + #[test] + fn fill_struct_short() { + check_assist( + fill_struct_fields, + r#" + struct S { + foo: u32, + bar: String, + } + + fn main() { + let s = S {<|> }; + } + "#, + r#" + struct S { + foo: u32, + bar: String, + } + + fn main() { + let s = <|>S { foo: (), bar: () }; + } + "#, + ); + } } -- cgit v1.2.3 From 5b2fd86d2523299c4a5ec58ad33c3474f52f85a2 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 22 Apr 2019 10:10:05 +0300 Subject: more type safety --- crates/ra_assists/src/ast_editor.rs | 25 ++++++++++++++++++++++++- crates/ra_assists/src/fill_struct_fields.rs | 6 ++++-- crates/ra_assists/src/lib.rs | 2 +- 3 files changed, 29 insertions(+), 4 deletions(-) (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 7ef58aa8e..13ee82879 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs @@ -137,9 +137,32 @@ pub struct AstBuilder { } impl AstBuilder { - pub fn from_text(text: &str) -> TreeArc { + fn from_text(text: &str) -> TreeArc { ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text)) } + + pub fn from_pieces(name: &ast::NameRef, expr: Option<&ast::Expr>) -> TreeArc { + match expr { + Some(expr) => Self::from_text(&format!("{}: {}", name.syntax(), expr.syntax())), + None => Self::from_text(&name.syntax().to_string()), + } + } +} + +impl AstBuilder { + fn from_text(text: &str) -> TreeArc { + ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) + } + + pub fn unit() -> TreeArc { + Self::from_text("()") + } +} + +impl AstBuilder { + pub fn new(text: &str) -> TreeArc { + ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) + } } fn ast_node_from_file_text(text: &str) -> TreeArc { diff --git a/crates/ra_assists/src/fill_struct_fields.rs b/crates/ra_assists/src/fill_struct_fields.rs index 302d62ab1..54b70e17d 100644 --- a/crates/ra_assists/src/fill_struct_fields.rs +++ b/crates/ra_assists/src/fill_struct_fields.rs @@ -37,8 +37,10 @@ pub(crate) fn fill_struct_fields(mut ctx: AssistCtx) -> Option }; for field in fields { - let field = - AstBuilder::::from_text(&format!("{}: ()", field.name(db))); + let field = AstBuilder::::from_pieces( + &AstBuilder::::new(&field.name(db).to_string()), + Some(&AstBuilder::::unit()), + ); ast_editor.append_field(&field); } diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 3151b1c44..60b4d5c63 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -37,7 +37,7 @@ pub struct AssistAction { pub target: Option, } -/// Return all the assists applicable at the given position. +/// Return all the assists eapplicable at the given position. /// /// Assists are returned in the "unresolved" state, that is only labels are /// returned, without actual edits. -- cgit v1.2.3 From 268e739c94d2e9edbb45374dfcc252b1648d3181 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 22 Apr 2019 13:01:33 +0300 Subject: move add_missing_members to structured editing API Currently, this is more code, and we also loose auto-indenting of bodies, but, long-term, this is the right approach --- crates/ra_assists/src/add_missing_impl_members.rs | 149 ++++++-------------- crates/ra_assists/src/ast_editor.rs | 158 +++++++++++++++++++--- 2 files changed, 178 insertions(+), 129 deletions(-) (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/add_missing_impl_members.rs b/crates/ra_assists/src/add_missing_impl_members.rs index c82447b84..17c2af899 100644 --- a/crates/ra_assists/src/add_missing_impl_members.rs +++ b/crates/ra_assists/src/add_missing_impl_members.rs @@ -1,14 +1,9 @@ -use std::fmt::Write; - -use crate::{Assist, AssistId, AssistCtx}; +use crate::{Assist, AssistId, AssistCtx, ast_editor::{AstEditor, AstBuilder}}; use hir::db::HirDatabase; -use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc}; -use ra_syntax::ast::{self, AstNode, AstToken, FnDef, ImplItem, ImplItemKind, NameOwner}; +use ra_syntax::{SmolStr, TreeArc}; +use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner}; use ra_db::FilePosition; -use ra_fmt::{leading_indent, reindent}; - -use itertools::Itertools; enum AddMissingImplMembersMode { DefaultMethodsOnly, @@ -76,48 +71,35 @@ fn add_missing_impl_members_inner( } ctx.add_action(AssistId(assist_id), label, |edit| { - let (parent_indent, indent) = { - // FIXME: Find a way to get the indent already used in the file. - // Now, we copy the indent of first item or indent with 4 spaces relative to impl block - const DEFAULT_INDENT: &str = " "; - let first_item = impl_item_list.impl_items().next(); - let first_item_indent = - first_item.and_then(|i| leading_indent(i.syntax())).map(ToOwned::to_owned); - let impl_block_indent = leading_indent(impl_node.syntax()).unwrap_or_default(); - - ( - impl_block_indent.to_owned(), - first_item_indent.unwrap_or_else(|| impl_block_indent.to_owned() + DEFAULT_INDENT), - ) - }; - - let changed_range = { - let children = impl_item_list.syntax().children_with_tokens(); - let last_whitespace = - children.filter_map(|it| ast::Whitespace::cast(it.as_token()?)).last(); - - last_whitespace.map(|w| w.syntax().range()).unwrap_or_else(|| { - let in_brackets = impl_item_list.syntax().range().end() - TextUnit::of_str("}"); - TextRange::from_to(in_brackets, in_brackets) - }) - }; - - let func_bodies = format!("\n{}", missing_fns.into_iter().map(build_func_body).join("\n")); - let trailing_whitespace = format!("\n{}", parent_indent); - let func_bodies = reindent(&func_bodies, &indent) + &trailing_whitespace; - - let replaced_text_range = TextUnit::of_str(&func_bodies); - - edit.replace(changed_range, func_bodies); - // FIXME: place the cursor on the first unimplemented? - edit.set_cursor( - changed_range.start() + replaced_text_range - TextUnit::of_str(&trailing_whitespace), - ); + let n_existing_items = impl_item_list.impl_items().count(); + let fns = missing_fns.into_iter().map(add_body_and_strip_docstring).collect::>(); + + let mut ast_editor = AstEditor::new(impl_item_list); + if n_existing_items == 0 { + ast_editor.make_multiline(); + } + ast_editor.append_functions(fns.iter().map(|it| &**it)); + let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); + let cursor_poisition = first_new_item.syntax().range().start(); + ast_editor.into_text_edit(edit.text_edit_builder()); + + edit.set_cursor(cursor_poisition); }); ctx.build() } +fn add_body_and_strip_docstring(fn_def: &ast::FnDef) -> TreeArc { + let mut ast_editor = AstEditor::new(fn_def); + if fn_def.body().is_none() { + ast_editor.set_body(&AstBuilder::::single_expr( + &AstBuilder::::unimplemented(), + )); + } + ast_editor.strip_attrs_and_docs(); + ast_editor.ast().to_owned() +} + /// Given an `ast::ImplBlock`, resolves the target trait (the one being /// implemented) to a `ast::TraitDef`. fn resolve_target_trait_def( @@ -134,22 +116,6 @@ fn resolve_target_trait_def( } } -fn build_func_body(def: &ast::FnDef) -> String { - let mut buf = String::new(); - - for child in def.syntax().children_with_tokens() { - match (child.prev_sibling_or_token().map(|c| c.kind()), child.kind()) { - (_, SyntaxKind::SEMI) => buf.push_str(" {\n unimplemented!()\n}"), - (_, SyntaxKind::ATTR) | (_, SyntaxKind::COMMENT) => {} - (Some(SyntaxKind::ATTR), SyntaxKind::WHITESPACE) - | (Some(SyntaxKind::COMMENT), SyntaxKind::WHITESPACE) => {} - _ => write!(buf, "{}", child).unwrap(), - }; - } - - buf.trim_end().to_string() -} - #[cfg(test)] mod tests { use super::*; @@ -170,7 +136,7 @@ struct S; impl Foo for S { fn bar(&self) {} - <|> +<|> }", " trait Foo { @@ -183,12 +149,9 @@ struct S; impl Foo for S { fn bar(&self) {} - fn foo(&self) { - unimplemented!() - } - fn baz(&self) { - unimplemented!() - }<|> + <|>fn foo(&self) { unimplemented!() } + fn baz(&self) { unimplemented!() } + }", ); } @@ -208,7 +171,7 @@ struct S; impl Foo for S { fn bar(&self) {} - <|> +<|> }", " trait Foo { @@ -221,9 +184,8 @@ struct S; impl Foo for S { fn bar(&self) {} - fn foo(&self) { - unimplemented!() - }<|> + <|>fn foo(&self) { unimplemented!() } + }", ); } @@ -240,9 +202,7 @@ impl Foo for S { <|> }", trait Foo { fn foo(&self); } struct S; impl Foo for S { - fn foo(&self) { - unimplemented!() - }<|> + <|>fn foo(&self) { unimplemented!() } }", ); } @@ -259,9 +219,7 @@ impl Foo for S {}<|>", trait Foo { fn foo(&self); } struct S; impl Foo for S { - fn foo(&self) { - unimplemented!() - }<|> + <|>fn foo(&self) { unimplemented!() } }", ) } @@ -291,35 +249,6 @@ impl Foo for S { <|> }", ) } - #[test] - fn test_indented_impl_block() { - check_assist( - add_missing_impl_members, - " -trait Foo { - fn valid(some: u32) -> bool; -} -struct S; - -mod my_mod { - impl crate::Foo for S { <|> } -}", - " -trait Foo { - fn valid(some: u32) -> bool; -} -struct S; - -mod my_mod { - impl crate::Foo for S { - fn valid(some: u32) -> bool { - unimplemented!() - }<|> - } -}", - ) - } - #[test] fn test_with_docstring_and_attrs() { check_assist( @@ -342,9 +271,7 @@ trait Foo { } struct S; impl Foo for S { - fn foo(&self) { - unimplemented!() - }<|> + <|>fn foo(&self) { unimplemented!() } }"#, ) } @@ -367,7 +294,7 @@ trait Foo { } struct S; impl Foo for S { - fn valid(some: u32) -> bool { false }<|> + <|>fn valid(some: u32) -> bool { false } }", ) } diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 13ee82879..283b280b6 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs @@ -1,4 +1,4 @@ -use std::iter; +use std::{iter, ops::RangeInclusive}; use arrayvec::ArrayVec; use ra_text_edit::TextEditBuilder; @@ -26,6 +26,7 @@ impl AstEditor { &*self.ast } + #[must_use] fn insert_children<'a>( &self, position: InsertPosition>, @@ -34,31 +35,55 @@ impl AstEditor { let new_syntax = self.ast().syntax().insert_children(position, to_insert); N::cast(&new_syntax).unwrap().to_owned() } -} -impl AstEditor { - pub fn append_field(&mut self, field: &ast::NamedField) { - self.insert_field(InsertPosition::Last, field) + #[must_use] + fn replace_children<'a>( + &self, + to_delete: RangeInclusive>, + to_insert: impl Iterator>, + ) -> TreeArc { + let new_syntax = self.ast().syntax().replace_children(to_delete, to_insert); + N::cast(&new_syntax).unwrap().to_owned() } - pub fn make_multiline(&mut self) { - let l_curly = match self.l_curly() { - Some(it) => it, - None => return, - }; + fn do_make_multiline(&mut self) { + let l_curly = + match self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) { + Some(it) => it, + None => return, + }; let sibling = match l_curly.next_sibling_or_token() { Some(it) => it, None => return, }; - if sibling.as_token().map(|it| it.text().contains('\n')) == Some(true) { - return; - } + let existing_ws = match sibling.as_token() { + None => None, + Some(tok) if tok.kind() != WHITESPACE => None, + Some(ws) => { + if ws.text().contains('\n') { + return; + } + Some(ws) + } + }; + + let indent = leading_indent(self.ast().syntax()).unwrap_or(""); + let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); + let to_insert = iter::once(ws.ws().into()); + self.ast = match existing_ws { + None => self.insert_children(InsertPosition::After(l_curly), to_insert), + Some(ws) => self.replace_children(RangeInclusive::new(ws.into(), ws.into()), to_insert), + }; + } +} + +impl AstEditor { + pub fn append_field(&mut self, field: &ast::NamedField) { + self.insert_field(InsertPosition::Last, field) + } - let ws = tokens::WsBuilder::new(&format!( - "\n{}", - leading_indent(self.ast().syntax()).unwrap_or("") - )); - self.ast = self.insert_children(InsertPosition::After(l_curly), iter::once(ws.ws().into())); + pub fn make_multiline(&mut self) { + self.do_make_multiline() } pub fn insert_field( @@ -132,6 +157,79 @@ impl AstEditor { } } +impl AstEditor { + pub fn make_multiline(&mut self) { + self.do_make_multiline() + } + + pub fn append_functions<'a>(&mut self, fns: impl Iterator) { + fns.for_each(|it| self.append_function(it)) + } + + pub fn append_function(&mut self, fn_def: &ast::FnDef) { + let (indent, position) = match self.ast().impl_items().last() { + Some(it) => ( + leading_indent(it.syntax()).unwrap_or("").to_string(), + InsertPosition::After(it.syntax().into()), + ), + None => match self.l_curly() { + Some(it) => ( + " ".to_string() + leading_indent(self.ast().syntax()).unwrap_or(""), + InsertPosition::After(it), + ), + None => return, + }, + }; + let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); + let to_insert: ArrayVec<[SyntaxElement; 2]> = + [ws.ws().into(), fn_def.syntax().into()].into(); + self.ast = self.insert_children(position, to_insert.into_iter()); + } + + fn l_curly(&self) -> Option { + self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) + } +} + +impl AstEditor { + pub fn set_body(&mut self, body: &ast::Block) { + let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new(); + let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.ast().body() { + old_body.syntax().into() + } else if let Some(semi) = self.ast().semicolon_token() { + to_insert.push(tokens::single_space().into()); + semi.into() + } else { + to_insert.push(tokens::single_space().into()); + to_insert.push(body.syntax().into()); + self.ast = self.insert_children(InsertPosition::Last, to_insert.into_iter()); + return; + }; + to_insert.push(body.syntax().into()); + let replace_range = RangeInclusive::new(old_body_or_semi, old_body_or_semi); + self.ast = self.replace_children(replace_range, to_insert.into_iter()) + } + + pub fn strip_attrs_and_docs(&mut self) { + loop { + if let Some(start) = self + .ast() + .syntax() + .children_with_tokens() + .find(|it| it.kind() == ATTR || it.kind() == COMMENT) + { + let end = match start.next_sibling_or_token() { + Some(el) if el.kind() == WHITESPACE => el, + Some(_) | None => start, + }; + self.ast = self.replace_children(RangeInclusive::new(start, end), iter::empty()); + } else { + break; + } + } + } +} + pub struct AstBuilder { _phantom: std::marker::PhantomData, } @@ -149,6 +247,16 @@ impl AstBuilder { } } +impl AstBuilder { + fn from_text(text: &str) -> TreeArc { + ast_node_from_file_text(&format!("fn f() {}", text)) + } + + pub fn single_expr(e: &ast::Expr) -> TreeArc { + Self::from_text(&format!("{{ {} }}", e.syntax())) + } +} + impl AstBuilder { fn from_text(text: &str) -> TreeArc { ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) @@ -157,6 +265,10 @@ impl AstBuilder { pub fn unit() -> TreeArc { Self::from_text("()") } + + pub fn unimplemented() -> TreeArc { + Self::from_text("unimplemented!()") + } } impl AstBuilder { @@ -197,6 +309,16 @@ mod tokens { .unwrap() } + #[allow(unused)] + pub(crate) fn single_newline() -> SyntaxToken<'static> { + SOURCE_FILE + .syntax() + .descendants_with_tokens() + .filter_map(|it| it.as_token()) + .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n") + .unwrap() + } + pub(crate) struct WsBuilder(TreeArc); impl WsBuilder { -- cgit v1.2.3 From b73a978b95810b188090a37e002a22403a9067bd Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 22 Apr 2019 13:05:01 +0300 Subject: drop useless test --- crates/ra_assists/src/ast_editor.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 283b280b6..6854294ae 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs @@ -331,31 +331,3 @@ mod tokens { } } - -#[cfg(test)] -mod tests { - use super::*; - - use ra_syntax::SourceFile; - - #[test] - fn structure_editing() { - let file = SourceFile::parse( - "\ -fn foo() { - let s = S { - original: 92, - } -} -", - ); - let field_list = file.syntax().descendants().find_map(ast::NamedFieldList::cast).unwrap(); - let mut editor = AstEditor::new(field_list); - - let field = AstBuilder::::from_text("first_inserted: 1"); - editor.append_field(&field); - let field = AstBuilder::::from_text("second_inserted: 2"); - editor.append_field(&field); - eprintln!("{}", editor.ast().syntax()); - } -} -- cgit v1.2.3