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 --- Cargo.lock | 2 + crates/ra_assists/Cargo.toml | 2 + crates/ra_assists/src/ast_editor.rs | 153 ++++++++++++++++++++++++++++++++++++ crates/ra_assists/src/lib.rs | 1 + crates/ra_syntax/src/lib.rs | 2 +- crates/ra_syntax/src/ptr.rs | 2 +- crates/ra_syntax/src/syntax_node.rs | 81 ++++++++++++++++++- 7 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 crates/ra_assists/src/ast_editor.rs diff --git a/Cargo.lock b/Cargo.lock index e5729f968..3be73d66d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -903,8 +903,10 @@ version = "0.1.0" name = "ra_assists" version = "0.1.0" dependencies = [ + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "ra_db 0.1.0", "ra_fmt 0.1.0", "ra_hir 0.1.0", diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index d4056a349..29d9ceb59 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml @@ -5,8 +5,10 @@ version = "0.1.0" authors = ["rust-analyzer developers"] [dependencies] +lazy_static = "1.3.0" join_to_string = "0.1.3" itertools = "0.8.0" +arrayvec = "0.4.10" ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } 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; diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index a6ce14f06..9cb66b76b 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -38,7 +38,7 @@ pub use crate::{ ast::AstNode, syntax_error::{SyntaxError, SyntaxErrorKind, Location}, syntax_text::SyntaxText, - syntax_node::{Direction, SyntaxNode, WalkEvent, TreeArc, SyntaxTreeBuilder, SyntaxElement, SyntaxToken}, + syntax_node::{Direction, SyntaxNode, WalkEvent, TreeArc, SyntaxTreeBuilder, SyntaxElement, SyntaxToken, InsertPosition}, ptr::{SyntaxNodePtr, AstPtr}, parsing::{tokenize, classify_literal, Token}, }; diff --git a/crates/ra_syntax/src/ptr.rs b/crates/ra_syntax/src/ptr.rs index 15a8b94cd..b0816b135 100644 --- a/crates/ra_syntax/src/ptr.rs +++ b/crates/ra_syntax/src/ptr.rs @@ -10,7 +10,7 @@ use crate::{ /// specific node across reparses of the same file. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct SyntaxNodePtr { - range: TextRange, + pub(crate) range: TextRange, kind: SyntaxKind, } diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs index dc2352c76..d9591781d 100644 --- a/crates/ra_syntax/src/syntax_node.rs +++ b/crates/ra_syntax/src/syntax_node.rs @@ -17,13 +17,20 @@ use ra_parser::ParseError; use rowan::{TransparentNewType, GreenNodeBuilder}; use crate::{ - SmolStr, SyntaxKind, TextUnit, TextRange, SyntaxText, SourceFile, AstNode, + SmolStr, SyntaxKind, TextUnit, TextRange, SyntaxText, SourceFile, AstNode, SyntaxNodePtr, syntax_error::{SyntaxError, SyntaxErrorKind}, }; pub use rowan::WalkEvent; pub(crate) use rowan::{GreenNode, GreenToken}; +pub enum InsertPosition { + First, + Last, + Before(T), + After(T), +} + /// Marker trait for CST and AST nodes pub trait SyntaxNodeWrapper: TransparentNewType {} impl> SyntaxNodeWrapper for T {} @@ -309,6 +316,71 @@ impl SyntaxNode { pub(crate) fn replace_with(&self, replacement: GreenNode) -> GreenNode { self.0.replace_with(replacement) } + + /// Adds specified children (tokens or nodes) to the current node at the + /// specific position. + /// + /// This is a type-unsafe low-level editing API, if you need to use it, + /// prefer to create a type-safe abstraction on top of it instead. + /// + /// + pub fn insert_children<'a>( + &self, + position: InsertPosition>, + to_insert: impl Iterator>, + ) -> TreeArc { + let mut delta = TextUnit::default(); + let to_insert = to_insert.map(|element| { + delta += element.text_len(); + to_green_element(element) + }); + + let old_children = self.0.green().children(); + + let get_anchor_pos = |anchor: SyntaxElement| -> usize { + self.children_with_tokens() + .position(|it| it == anchor) + .expect("anchor is not a child of current element") + }; + + let new_children = match position { + InsertPosition::First => { + to_insert.chain(old_children.iter().cloned()).collect::>() + } + InsertPosition::Last => { + old_children.iter().cloned().chain(to_insert).collect::>() + } + InsertPosition::Before(anchor) | InsertPosition::After(anchor) => { + let take_anchor = if let InsertPosition::After(_) = position { 1 } else { 0 }; + let (before, after) = old_children.split_at(get_anchor_pos(anchor) + take_anchor); + before + .iter() + .cloned() + .chain(to_insert) + .chain(after.iter().cloned()) + .collect::>() + } + }; + + let new_node = GreenNode::new(rowan::SyntaxKind(self.kind() as u16), new_children); + let new_file_node = self.replace_with(new_node); + let file = SourceFile::new(new_file_node, Vec::new()); + + // FIXME: use a more elegant way to re-fetch the node (#1185), make + // `range` private afterwards + let mut ptr = SyntaxNodePtr::new(self); + ptr.range = TextRange::from_to(ptr.range().start(), ptr.range().end() + delta); + return ptr.to_node(&file).to_owned(); + + fn to_green_element(element: SyntaxElement) -> rowan::GreenElement { + match element { + SyntaxElement::Node(node) => node.0.green().clone().into(), + SyntaxElement::Token(tok) => { + GreenToken::new(rowan::SyntaxKind(tok.kind() as u16), tok.text().clone()).into() + } + } + } + } } #[derive(Clone, Copy, PartialEq, Eq, Hash)] @@ -451,6 +523,13 @@ impl<'a> SyntaxElement<'a> { } .ancestors() } + + fn text_len(&self) -> TextUnit { + match self { + SyntaxElement::Node(node) => node.0.green().text_len(), + SyntaxElement::Token(token) => TextUnit::of_str(token.0.text()), + } + } } impl<'a> From> for SyntaxElement<'a> { -- 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(-) 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 +++++++++++++++++++++++++++ crates/ra_syntax/src/syntax_node.rs | 1 + 3 files changed, 39 insertions(+), 4 deletions(-) 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: () }; + } + "#, + ); + } } diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs index d9591781d..628cabc29 100644 --- a/crates/ra_syntax/src/syntax_node.rs +++ b/crates/ra_syntax/src/syntax_node.rs @@ -24,6 +24,7 @@ use crate::{ pub use rowan::WalkEvent; pub(crate) use rowan::{GreenNode, GreenToken}; +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum InsertPosition { First, Last, -- 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(-) 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 b811922a530b7b22469b386a521946f1003911c7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 22 Apr 2019 10:38:39 +0300 Subject: fix postfix match indent --- crates/ra_ide_api/src/completion/complete_postfix.rs | 2 +- ..._item__postfix_completion_works_for_trivial_path_expression.snap | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs index 4dfa5f176..278b1b797 100644 --- a/crates/ra_ide_api/src/completion/complete_postfix.rs +++ b/crates/ra_ide_api/src/completion/complete_postfix.rs @@ -40,7 +40,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { ctx, "match", "match expr {}", - &format!("match {} {{\n${{1:_}} => {{$0\\}},\n}}", receiver_text), + &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), ) .add_to(acc); postfix_snippet( diff --git a/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap b/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap index 2b5435f0c..3bbc9e3c4 100644 --- a/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap +++ b/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap @@ -1,6 +1,6 @@ --- -created: "2019-02-18T09:22:24.127119709Z" -creator: insta@0.6.2 +created: "2019-04-22T07:37:13.981826301Z" +creator: insta@0.7.4 source: crates/ra_ide_api/src/completion/completion_item.rs expression: kind_completions --- @@ -23,7 +23,7 @@ expression: kind_completions label: "match", source_range: [76; 76), delete: [72; 76), - insert: "match bar {\n${1:_} => {$0\\},\n}", + insert: "match bar {\n ${1:_} => {$0\\},\n}", detail: "match expr {}" }, CompletionItem { -- 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 +++++++++++++++++++--- crates/ra_syntax/src/ast/extensions.rs | 9 ++ crates/ra_syntax/src/syntax_node.rs | 61 ++++++--- 4 files changed, 231 insertions(+), 146 deletions(-) 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 { diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index 5c4c0ffc1..9cbd2c6b8 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs @@ -210,6 +210,15 @@ impl ast::EnumVariant { } } +impl ast::FnDef { + pub fn semicolon_token(&self) -> Option> { + self.syntax() + .last_child_or_token() + .and_then(|it| it.as_token()) + .filter(|it| it.kind() == SEMI) + } +} + impl ast::LetStmt { pub fn has_semi(&self) -> bool { match self.syntax().last_child_or_token() { diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs index 628cabc29..92c15234e 100644 --- a/crates/ra_syntax/src/syntax_node.rs +++ b/crates/ra_syntax/src/syntax_node.rs @@ -7,6 +7,7 @@ //! modules just wraps its API. use std::{ + ops::RangeInclusive, fmt::{self, Write}, any::Any, borrow::Borrow, @@ -323,8 +324,6 @@ impl SyntaxNode { /// /// This is a type-unsafe low-level editing API, if you need to use it, /// prefer to create a type-safe abstraction on top of it instead. - /// - /// pub fn insert_children<'a>( &self, position: InsertPosition>, @@ -338,12 +337,6 @@ impl SyntaxNode { let old_children = self.0.green().children(); - let get_anchor_pos = |anchor: SyntaxElement| -> usize { - self.children_with_tokens() - .position(|it| it == anchor) - .expect("anchor is not a child of current element") - }; - let new_children = match position { InsertPosition::First => { to_insert.chain(old_children.iter().cloned()).collect::>() @@ -353,7 +346,8 @@ impl SyntaxNode { } InsertPosition::Before(anchor) | InsertPosition::After(anchor) => { let take_anchor = if let InsertPosition::After(_) = position { 1 } else { 0 }; - let (before, after) = old_children.split_at(get_anchor_pos(anchor) + take_anchor); + let split_at = self.position_of_child(anchor) + take_anchor; + let (before, after) = old_children.split_at(split_at); before .iter() .cloned() @@ -363,6 +357,33 @@ impl SyntaxNode { } }; + self.with_children(new_children) + } + + /// Replaces all nodes in `to_delete` with nodes from `to_insert` + /// + /// This is a type-unsafe low-level editing API, if you need to use it, + /// prefer to create a type-safe abstraction on top of it instead. + pub fn replace_children<'a>( + &self, + to_delete: RangeInclusive>, + to_insert: impl Iterator>, + ) -> TreeArc { + let start = self.position_of_child(*to_delete.start()); + let end = self.position_of_child(*to_delete.end()); + let old_children = self.0.green().children(); + + let new_children = old_children[..start] + .iter() + .cloned() + .chain(to_insert.map(to_green_element)) + .chain(old_children[end + 1..].iter().cloned()) + .collect::>(); + self.with_children(new_children) + } + + fn with_children(&self, new_children: Box<[rowan::GreenElement]>) -> TreeArc { + let len = new_children.iter().map(|it| it.text_len()).sum::(); let new_node = GreenNode::new(rowan::SyntaxKind(self.kind() as u16), new_children); let new_file_node = self.replace_with(new_node); let file = SourceFile::new(new_file_node, Vec::new()); @@ -370,16 +391,22 @@ impl SyntaxNode { // FIXME: use a more elegant way to re-fetch the node (#1185), make // `range` private afterwards let mut ptr = SyntaxNodePtr::new(self); - ptr.range = TextRange::from_to(ptr.range().start(), ptr.range().end() + delta); + ptr.range = TextRange::offset_len(ptr.range().start(), len); return ptr.to_node(&file).to_owned(); + } - fn to_green_element(element: SyntaxElement) -> rowan::GreenElement { - match element { - SyntaxElement::Node(node) => node.0.green().clone().into(), - SyntaxElement::Token(tok) => { - GreenToken::new(rowan::SyntaxKind(tok.kind() as u16), tok.text().clone()).into() - } - } + fn position_of_child(&self, child: SyntaxElement) -> usize { + self.children_with_tokens() + .position(|it| it == child) + .expect("elemetn is not a child of current element") + } +} + +fn to_green_element(element: SyntaxElement) -> rowan::GreenElement { + match element { + SyntaxElement::Node(node) => node.0.green().clone().into(), + SyntaxElement::Token(tok) => { + GreenToken::new(rowan::SyntaxKind(tok.kind() as u16), tok.text().clone()).into() } } } -- 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(-) 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