From 5dbbfda34ae423229487595fd0ae9e727ae42906 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 28 Sep 2019 19:50:16 +0300 Subject: simplify strip attrs --- crates/ra_syntax/src/ast.rs | 2 +- crates/ra_syntax/src/ast/edit.rs | 21 ++++++++++++++++++++- crates/ra_syntax/src/ast/extensions.rs | 12 +++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) (limited to 'crates/ra_syntax/src') diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index fdffd8cb1..1b2ce921a 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -5,7 +5,7 @@ mod traits; mod tokens; mod extensions; mod expr_extensions; -mod edit; +pub mod edit; pub mod make; use std::marker::PhantomData; diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index c65899812..7013cc9b5 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -1,14 +1,16 @@ //! This module contains functions for editing syntax trees. As the trees are //! immutable, all function here return a fresh copy of the tree, instead of //! doing an in-place modification. +use std::{iter, ops::RangeInclusive}; use arrayvec::ArrayVec; -use std::ops::RangeInclusive; use crate::{ algo, ast::{self, make, AstNode}, InsertPosition, SyntaxElement, + SyntaxKind::{ATTR, COMMENT, WHITESPACE}, + SyntaxNode, }; impl ast::FnDef { @@ -31,6 +33,23 @@ impl ast::FnDef { } } +pub fn strip_attrs_and_docs(node: N) -> N { + N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() +} + +fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { + while let Some(start) = + node.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.clone(), + Some(_) | None => start.clone(), + }; + node = algo::replace_children(&node, RangeInclusive::new(start, end), &mut iter::empty()); + } + node +} + #[must_use] fn insert_children( parent: &N, diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index 0433edb84..8c5ece65d 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use crate::{ - ast::{self, child_opt, children, AstNode, SyntaxNode}, + ast::{self, child_opt, children, AstChildren, AstNode, SyntaxNode}, SmolStr, SyntaxElement, SyntaxKind::*, SyntaxToken, T, @@ -203,6 +203,16 @@ impl ast::ImplBlock { } } +impl ast::AttrsOwner for ast::ImplItem { + fn attrs(&self) -> AstChildren { + match self { + ast::ImplItem::FnDef(it) => it.attrs(), + ast::ImplItem::TypeAliasDef(it) => it.attrs(), + ast::ImplItem::ConstDef(it) => it.attrs(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum StructKind { Tuple(ast::TupleFieldDefList), -- cgit v1.2.3 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_syntax/src/ast/edit.rs | 97 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) (limited to 'crates/ra_syntax/src') 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 From e010b144d5abcbd0947d0490123ef693a6a17c78 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Sep 2019 09:27:26 +0300 Subject: move field list to ast/edit.rs --- crates/ra_syntax/src/algo.rs | 17 ++++++++- crates/ra_syntax/src/ast/edit.rs | 82 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) (limited to 'crates/ra_syntax/src') diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 46680a08f..f33d2ad4e 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs @@ -3,6 +3,7 @@ pub mod visit; use std::ops::RangeInclusive; use itertools::Itertools; +use ra_text_edit::TextEditBuilder; use rustc_hash::FxHashMap; use crate::{ @@ -63,6 +64,18 @@ pub enum InsertPosition { After(T), } +pub struct TreeDiff { + replacements: FxHashMap, +} + +impl TreeDiff { + pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { + for (from, to) in self.replacements.iter() { + builder.replace(from.text_range(), to.to_string()) + } + } +} + /// Finds minimal the diff, which, applied to `from`, will result in `to`. /// /// Specifically, returns a map whose keys are descendants of `from` and values @@ -70,12 +83,12 @@ pub enum InsertPosition { /// /// A trivial solution is a singletom map `{ from: to }`, but this function /// tries to find a more fine-grained diff. -pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap { +pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { let mut buf = FxHashMap::default(); // FIXME: this is both horrible inefficient and gives larger than // necessary diff. I bet there's a cool algorithm to diff trees properly. go(&mut buf, from.clone().into(), to.clone().into()); - return buf; + return TreeDiff { replacements: buf }; fn go( buf: &mut FxHashMap, diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 2af6f573e..6e64c0675 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -12,7 +12,7 @@ use crate::{ make::{self, tokens}, AstNode, }, - AstToken, InsertPosition, SmolStr, SyntaxElement, + AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind::{ATTR, COMMENT, WHITESPACE}, SyntaxNode, T, }; @@ -105,6 +105,86 @@ impl ast::ItemList { } } +impl ast::RecordFieldList { + #[must_use] + pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList { + self.insert_field(InsertPosition::Last, field) + } + + #[must_use] + pub fn insert_field( + &self, + position: InsertPosition<&'_ ast::RecordField>, + field: &ast::RecordField, + ) -> ast::RecordFieldList { + let is_multiline = self.syntax().text().contains_char('\n'); + let ws; + let space = if is_multiline { + ws = tokens::WsBuilder::new(&format!( + "\n{} ", + leading_indent(self.syntax()).unwrap_or("".into()) + )); + 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().clone().into()); + to_insert.push(tokens::comma().into()); + + macro_rules! after_l_curly { + () => {{ + let anchor = match self.l_curly() { + Some(it) => it, + None => return self.clone(), + }; + InsertPosition::After(anchor) + }}; + } + + macro_rules! after_field { + ($anchor:expr) => { + if let Some(comma) = $anchor + .syntax() + .siblings_with_tokens(Direction::Next) + .find(|it| it.kind() == T![,]) + { + InsertPosition::After(comma) + } else { + to_insert.insert(0, tokens::comma().into()); + InsertPosition::After($anchor.syntax().clone().into()) + } + }; + }; + + let position = match position { + InsertPosition::First => after_l_curly!(), + InsertPosition::Last => { + if !is_multiline { + // don't insert comma before curly + to_insert.pop(); + } + match self.fields().last() { + Some(it) => after_field!(it), + None => after_l_curly!(), + } + } + InsertPosition::Before(anchor) => { + InsertPosition::Before(anchor.syntax().clone().into()) + } + InsertPosition::After(anchor) => after_field!(anchor), + }; + + insert_children(self, position, to_insert.iter().cloned()) + } + + fn l_curly(&self) -> Option { + self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) + } +} + pub fn strip_attrs_and_docs(node: N) -> N { N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() } -- cgit v1.2.3 From 054c53aeb9a9e29d1c06fa183da263037aa62572 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Sep 2019 09:56:20 +0300 Subject: move remove bounds to ast/edit.rs --- crates/ra_syntax/src/ast/edit.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'crates/ra_syntax/src') diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 6e64c0675..9d0fd1383 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -10,7 +10,7 @@ use crate::{ ast::{ self, make::{self, tokens}, - AstNode, + AstNode, TypeBoundsOwner, }, AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind::{ATTR, COMMENT, WHITESPACE}, @@ -185,6 +185,20 @@ impl ast::RecordFieldList { } } +impl ast::TypeParam { + pub fn remove_bounds(&self) -> ast::TypeParam { + let colon = match self.colon_token() { + Some(it) => it, + None => return self.clone(), + }; + let end = match self.type_bound_list() { + Some(it) => it.syntax().clone().into(), + None => colon.clone().into(), + }; + replace_children(self, RangeInclusive::new(colon.into(), end), iter::empty()) + } +} + pub fn strip_attrs_and_docs(node: N) -> N { N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() } -- cgit v1.2.3 From 05ca252fb51bbbf60433bdd3af55ce14bbd66bfd Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Sep 2019 10:05:12 +0300 Subject: remove ast_editor.rs --- crates/ra_syntax/src/ast/edit.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'crates/ra_syntax/src') diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 9d0fd1383..d0857d88b 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -4,6 +4,7 @@ use std::{iter, ops::RangeInclusive}; use arrayvec::ArrayVec; +use rustc_hash::FxHashMap; use crate::{ algo, @@ -216,6 +217,17 @@ fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { node } +pub fn replace_descendants( + parent: &N, + replacement_map: impl Iterator, +) -> N { + let map = replacement_map + .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) + .collect::>(); + let new_syntax = algo::replace_descendants(parent.syntax(), &map); + N::cast(new_syntax).unwrap() +} + // 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... -- cgit v1.2.3 From 4acadbdca61e77368061a0c53125e164912ab5d5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Sep 2019 10:08:28 +0300 Subject: cleanup editor --- crates/ra_syntax/src/ast/edit.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'crates/ra_syntax/src') diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index d0857d88b..03f3b5fbb 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -187,6 +187,7 @@ impl ast::RecordFieldList { } impl ast::TypeParam { + #[must_use] pub fn remove_bounds(&self) -> ast::TypeParam { let colon = match self.colon_token() { Some(it) => it, @@ -200,7 +201,8 @@ impl ast::TypeParam { } } -pub fn strip_attrs_and_docs(node: N) -> N { +#[must_use] +pub fn strip_attrs_and_docs(node: &N) -> N { N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() } @@ -217,6 +219,7 @@ fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { node } +#[must_use] pub fn replace_descendants( parent: &N, replacement_map: impl Iterator, -- cgit v1.2.3