From 0840ec038b2822a424acf238d8db5af569f99a21 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 28 Sep 2019 20:09:57 +0300 Subject: migrate add impl items to the new editing API --- crates/ra_assists/src/assist_ctx.rs | 8 +- .../src/assists/add_missing_impl_members.rs | 15 ++-- crates/ra_assists/src/ast_editor.rs | 69 +-------------- crates/ra_syntax/src/ast/edit.rs | 97 +++++++++++++++++++++- 4 files changed, 109 insertions(+), 80 deletions(-) diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index c45262efa..cbe12e908 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs @@ -2,7 +2,7 @@ use hir::db::HirDatabase; use ra_db::FileRange; use ra_fmt::{leading_indent, reindent}; use ra_syntax::{ - algo::{find_covering_element, find_node_at_offset}, + algo::{self, find_covering_element, find_node_at_offset}, AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, }; @@ -177,6 +177,12 @@ impl AssistBuilder { &mut self.edit } + pub(crate) fn replace_ast(&mut self, old: N, new: N) { + for (from, to) in algo::diff(old.syntax(), new.syntax()) { + self.edit.replace(from.text_range(), to.to_string()) + } + } + fn build(self) -> AssistAction { AssistAction { edit: self.edit.finish(), diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs index 3fce4a5b7..c2e3eb06b 100644 --- a/crates/ra_assists/src/assists/add_missing_impl_members.rs +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs @@ -4,7 +4,7 @@ use ra_syntax::{ SmolStr, }; -use crate::{ast_editor::AstEditor, Assist, AssistCtx, AssistId}; +use crate::{Assist, AssistCtx, AssistId}; #[derive(PartialEq)] enum AddMissingImplMembersMode { @@ -79,14 +79,13 @@ fn add_missing_impl_members_inner( ast::ImplItem::FnDef(def) => edit::strip_attrs_and_docs(add_body(def).into()), _ => edit::strip_attrs_and_docs(it), }); - let mut ast_editor = AstEditor::new(impl_item_list); - - ast_editor.append_items(items); - - let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); - let cursor_position = first_new_item.syntax().text_range().start(); - ast_editor.into_text_edit(edit.text_edit_builder()); + let new_impl_item_list = impl_item_list.append_items(items); + let cursor_position = { + let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap(); + first_new_item.syntax().text_range().start() + }; + edit.replace_ast(impl_item_list, new_impl_item_list); edit.set_cursor(cursor_position); }); diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 60b8923e1..262e2fcf4 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs @@ -7,9 +7,7 @@ use ra_fmt::leading_indent; use ra_syntax::{ algo, ast::{self, make::tokens, TypeBoundsOwner}, - AstNode, Direction, InsertPosition, SyntaxElement, - SyntaxKind::*, - T, + AstNode, Direction, InsertPosition, SyntaxElement, T, }; use ra_text_edit::TextEditBuilder; @@ -67,38 +65,6 @@ impl AstEditor { let new_syntax = algo::replace_children(self.ast().syntax(), to_delete, &mut to_insert); N::cast(new_syntax).unwrap() } - - fn do_make_multiline(&mut self) { - let l_curly = - match self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { - Some(it) => it, - None => return, - }; - let sibling = match l_curly.next_sibling_or_token() { - Some(it) => it, - None => 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.clone()) - } - }; - - let indent = leading_indent(self.ast().syntax()).unwrap_or("".into()); - 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.clone().into(), ws.into()), to_insert) - } - }; - } } impl AstEditor { @@ -179,39 +145,6 @@ impl AstEditor { } } -impl AstEditor { - pub fn append_items(&mut self, items: impl Iterator) { - if !self.ast().syntax().text().contains_char('\n') { - self.do_make_multiline(); - } - items.for_each(|it| self.append_item(it)); - } - - pub fn append_item(&mut self, item: ast::ImplItem) { - let (indent, position) = match self.ast().impl_items().last() { - Some(it) => ( - leading_indent(it.syntax()).unwrap_or_default().to_string(), - InsertPosition::After(it.syntax().clone().into()), - ), - None => match self.l_curly() { - Some(it) => ( - " ".to_string() + &leading_indent(self.ast().syntax()).unwrap_or_default(), - InsertPosition::After(it), - ), - None => return, - }, - }; - let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); - let to_insert: ArrayVec<[SyntaxElement; 2]> = - [ws.ws().into(), item.syntax().clone().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() == T!['{']) - } -} - impl AstEditor { pub fn remove_bounds(&mut self) -> &mut Self { let colon = match self.ast.colon_token() { diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 7013cc9b5..2af6f573e 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -7,10 +7,14 @@ use arrayvec::ArrayVec; use crate::{ algo, - ast::{self, make, AstNode}, - InsertPosition, SyntaxElement, + ast::{ + self, + make::{self, tokens}, + AstNode, + }, + AstToken, InsertPosition, SmolStr, SyntaxElement, SyntaxKind::{ATTR, COMMENT, WHITESPACE}, - SyntaxNode, + SyntaxNode, T, }; impl ast::FnDef { @@ -33,6 +37,74 @@ impl ast::FnDef { } } +impl ast::ItemList { + #[must_use] + pub fn append_items(&self, items: impl Iterator) -> ast::ItemList { + let mut res = self.clone(); + if !self.syntax().text().contains_char('\n') { + res = res.make_multiline(); + } + items.for_each(|it| res = res.append_item(it)); + res + } + + #[must_use] + pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList { + let (indent, position) = match self.impl_items().last() { + Some(it) => ( + leading_indent(it.syntax()).unwrap_or_default().to_string(), + InsertPosition::After(it.syntax().clone().into()), + ), + None => match self.l_curly() { + Some(it) => ( + " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(), + InsertPosition::After(it), + ), + None => return self.clone(), + }, + }; + let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); + let to_insert: ArrayVec<[SyntaxElement; 2]> = + [ws.ws().into(), item.syntax().clone().into()].into(); + insert_children(self, position, to_insert.into_iter()) + } + + fn l_curly(&self) -> Option { + self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) + } + + fn make_multiline(&self) -> ast::ItemList { + let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { + Some(it) => it, + None => return self.clone(), + }; + let sibling = match l_curly.next_sibling_or_token() { + Some(it) => it, + None => return self.clone(), + }; + let existing_ws = match sibling.as_token() { + None => None, + Some(tok) if tok.kind() != WHITESPACE => None, + Some(ws) => { + if ws.text().contains('\n') { + return self.clone(); + } + Some(ws.clone()) + } + }; + + let indent = leading_indent(self.syntax()).unwrap_or("".into()); + let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); + let to_insert = iter::once(ws.ws().into()); + match existing_ws { + None => insert_children(self, InsertPosition::After(l_curly), to_insert), + Some(ws) => { + replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert) + } + } + } +} + pub fn strip_attrs_and_docs(node: N) -> N { N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() } @@ -50,6 +122,25 @@ fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { node } +// Note this is copy-pasted from fmt. It seems like fmt should be a separate +// crate, but basic tree building should be this crate. However, tree building +// might want to call into fmt... +fn leading_indent(node: &SyntaxNode) -> Option { + let prev_tokens = std::iter::successors(node.first_token(), |token| token.prev_token()); + for token in prev_tokens { + if let Some(ws) = ast::Whitespace::cast(token.clone()) { + let ws_text = ws.text(); + if let Some(pos) = ws_text.rfind('\n') { + return Some(ws_text[pos + 1..].into()); + } + } + if token.text().contains('\n') { + break; + } + } + None +} + #[must_use] fn insert_children( parent: &N, -- cgit v1.2.3