aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_syntax/src')
-rw-r--r--crates/ra_syntax/src/algo.rs17
-rw-r--r--crates/ra_syntax/src/ast.rs2
-rw-r--r--crates/ra_syntax/src/ast/edit.rs225
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs83
-rw-r--r--crates/ra_syntax/src/ast/generated.rs43
-rw-r--r--crates/ra_syntax/src/ast/traits.rs2
-rw-r--r--crates/ra_syntax/src/grammar.ron3
7 files changed, 316 insertions, 59 deletions
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs
index e76f542ce..d55534ede 100644
--- a/crates/ra_syntax/src/algo.rs
+++ b/crates/ra_syntax/src/algo.rs
@@ -5,6 +5,7 @@ pub mod visit;
5use std::ops::RangeInclusive; 5use std::ops::RangeInclusive;
6 6
7use itertools::Itertools; 7use itertools::Itertools;
8use ra_text_edit::TextEditBuilder;
8use rustc_hash::FxHashMap; 9use rustc_hash::FxHashMap;
9 10
10use crate::{ 11use crate::{
@@ -65,6 +66,18 @@ pub enum InsertPosition<T> {
65 After(T), 66 After(T),
66} 67}
67 68
69pub struct TreeDiff {
70 replacements: FxHashMap<SyntaxElement, SyntaxElement>,
71}
72
73impl TreeDiff {
74 pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
75 for (from, to) in self.replacements.iter() {
76 builder.replace(from.text_range(), to.to_string())
77 }
78 }
79}
80
68/// Finds minimal the diff, which, applied to `from`, will result in `to`. 81/// Finds minimal the diff, which, applied to `from`, will result in `to`.
69/// 82///
70/// Specifically, returns a map whose keys are descendants of `from` and values 83/// Specifically, returns a map whose keys are descendants of `from` and values
@@ -72,12 +85,12 @@ pub enum InsertPosition<T> {
72/// 85///
73/// A trivial solution is a singletom map `{ from: to }`, but this function 86/// A trivial solution is a singletom map `{ from: to }`, but this function
74/// tries to find a more fine-grained diff. 87/// tries to find a more fine-grained diff.
75pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap<SyntaxElement, SyntaxElement> { 88pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
76 let mut buf = FxHashMap::default(); 89 let mut buf = FxHashMap::default();
77 // FIXME: this is both horrible inefficient and gives larger than 90 // FIXME: this is both horrible inefficient and gives larger than
78 // necessary diff. I bet there's a cool algorithm to diff trees properly. 91 // necessary diff. I bet there's a cool algorithm to diff trees properly.
79 go(&mut buf, from.clone().into(), to.clone().into()); 92 go(&mut buf, from.clone().into(), to.clone().into());
80 return buf; 93 return TreeDiff { replacements: buf };
81 94
82 fn go( 95 fn go(
83 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, 96 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>,
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;
5mod tokens; 5mod tokens;
6mod extensions; 6mod extensions;
7mod expr_extensions; 7mod expr_extensions;
8mod edit; 8pub mod edit;
9pub mod make; 9pub mod make;
10 10
11use std::marker::PhantomData; 11use std::marker::PhantomData;
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index c65899812..03f3b5fbb 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -1,14 +1,21 @@
1//! This module contains functions for editing syntax trees. As the trees are 1//! This module contains functions for editing syntax trees. As the trees are
2//! immutable, all function here return a fresh copy of the tree, instead of 2//! immutable, all function here return a fresh copy of the tree, instead of
3//! doing an in-place modification. 3//! doing an in-place modification.
4use std::{iter, ops::RangeInclusive};
4 5
5use arrayvec::ArrayVec; 6use arrayvec::ArrayVec;
6use std::ops::RangeInclusive; 7use rustc_hash::FxHashMap;
7 8
8use crate::{ 9use crate::{
9 algo, 10 algo,
10 ast::{self, make, AstNode}, 11 ast::{
11 InsertPosition, SyntaxElement, 12 self,
13 make::{self, tokens},
14 AstNode, TypeBoundsOwner,
15 },
16 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement,
17 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
18 SyntaxNode, T,
12}; 19};
13 20
14impl ast::FnDef { 21impl ast::FnDef {
@@ -31,6 +38,218 @@ impl ast::FnDef {
31 } 38 }
32} 39}
33 40
41impl ast::ItemList {
42 #[must_use]
43 pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList {
44 let mut res = self.clone();
45 if !self.syntax().text().contains_char('\n') {
46 res = res.make_multiline();
47 }
48 items.for_each(|it| res = res.append_item(it));
49 res
50 }
51
52 #[must_use]
53 pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList {
54 let (indent, position) = match self.impl_items().last() {
55 Some(it) => (
56 leading_indent(it.syntax()).unwrap_or_default().to_string(),
57 InsertPosition::After(it.syntax().clone().into()),
58 ),
59 None => match self.l_curly() {
60 Some(it) => (
61 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
62 InsertPosition::After(it),
63 ),
64 None => return self.clone(),
65 },
66 };
67 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
68 let to_insert: ArrayVec<[SyntaxElement; 2]> =
69 [ws.ws().into(), item.syntax().clone().into()].into();
70 insert_children(self, position, to_insert.into_iter())
71 }
72
73 fn l_curly(&self) -> Option<SyntaxElement> {
74 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
75 }
76
77 fn make_multiline(&self) -> ast::ItemList {
78 let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
79 Some(it) => it,
80 None => return self.clone(),
81 };
82 let sibling = match l_curly.next_sibling_or_token() {
83 Some(it) => it,
84 None => return self.clone(),
85 };
86 let existing_ws = match sibling.as_token() {
87 None => None,
88 Some(tok) if tok.kind() != WHITESPACE => None,
89 Some(ws) => {
90 if ws.text().contains('\n') {
91 return self.clone();
92 }
93 Some(ws.clone())
94 }
95 };
96
97 let indent = leading_indent(self.syntax()).unwrap_or("".into());
98 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
99 let to_insert = iter::once(ws.ws().into());
100 match existing_ws {
101 None => insert_children(self, InsertPosition::After(l_curly), to_insert),
102 Some(ws) => {
103 replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert)
104 }
105 }
106 }
107}
108
109impl ast::RecordFieldList {
110 #[must_use]
111 pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList {
112 self.insert_field(InsertPosition::Last, field)
113 }
114
115 #[must_use]
116 pub fn insert_field(
117 &self,
118 position: InsertPosition<&'_ ast::RecordField>,
119 field: &ast::RecordField,
120 ) -> ast::RecordFieldList {
121 let is_multiline = self.syntax().text().contains_char('\n');
122 let ws;
123 let space = if is_multiline {
124 ws = tokens::WsBuilder::new(&format!(
125 "\n{} ",
126 leading_indent(self.syntax()).unwrap_or("".into())
127 ));
128 ws.ws()
129 } else {
130 tokens::single_space()
131 };
132
133 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
134 to_insert.push(space.into());
135 to_insert.push(field.syntax().clone().into());
136 to_insert.push(tokens::comma().into());
137
138 macro_rules! after_l_curly {
139 () => {{
140 let anchor = match self.l_curly() {
141 Some(it) => it,
142 None => return self.clone(),
143 };
144 InsertPosition::After(anchor)
145 }};
146 }
147
148 macro_rules! after_field {
149 ($anchor:expr) => {
150 if let Some(comma) = $anchor
151 .syntax()
152 .siblings_with_tokens(Direction::Next)
153 .find(|it| it.kind() == T![,])
154 {
155 InsertPosition::After(comma)
156 } else {
157 to_insert.insert(0, tokens::comma().into());
158 InsertPosition::After($anchor.syntax().clone().into())
159 }
160 };
161 };
162
163 let position = match position {
164 InsertPosition::First => after_l_curly!(),
165 InsertPosition::Last => {
166 if !is_multiline {
167 // don't insert comma before curly
168 to_insert.pop();
169 }
170 match self.fields().last() {
171 Some(it) => after_field!(it),
172 None => after_l_curly!(),
173 }
174 }
175 InsertPosition::Before(anchor) => {
176 InsertPosition::Before(anchor.syntax().clone().into())
177 }
178 InsertPosition::After(anchor) => after_field!(anchor),
179 };
180
181 insert_children(self, position, to_insert.iter().cloned())
182 }
183
184 fn l_curly(&self) -> Option<SyntaxElement> {
185 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
186 }
187}
188
189impl ast::TypeParam {
190 #[must_use]
191 pub fn remove_bounds(&self) -> ast::TypeParam {
192 let colon = match self.colon_token() {
193 Some(it) => it,
194 None => return self.clone(),
195 };
196 let end = match self.type_bound_list() {
197 Some(it) => it.syntax().clone().into(),
198 None => colon.clone().into(),
199 };
200 replace_children(self, RangeInclusive::new(colon.into(), end), iter::empty())
201 }
202}
203
204#[must_use]
205pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
206 N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
207}
208
209fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode {
210 while let Some(start) =
211 node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT)
212 {
213 let end = match &start.next_sibling_or_token() {
214 Some(el) if el.kind() == WHITESPACE => el.clone(),
215 Some(_) | None => start.clone(),
216 };
217 node = algo::replace_children(&node, RangeInclusive::new(start, end), &mut iter::empty());
218 }
219 node
220}
221
222#[must_use]
223pub fn replace_descendants<N: AstNode, D: AstNode>(
224 parent: &N,
225 replacement_map: impl Iterator<Item = (D, D)>,
226) -> N {
227 let map = replacement_map
228 .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into()))
229 .collect::<FxHashMap<_, _>>();
230 let new_syntax = algo::replace_descendants(parent.syntax(), &map);
231 N::cast(new_syntax).unwrap()
232}
233
234// Note this is copy-pasted from fmt. It seems like fmt should be a separate
235// crate, but basic tree building should be this crate. However, tree building
236// might want to call into fmt...
237fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
238 let prev_tokens = std::iter::successors(node.first_token(), |token| token.prev_token());
239 for token in prev_tokens {
240 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
241 let ws_text = ws.text();
242 if let Some(pos) = ws_text.rfind('\n') {
243 return Some(ws_text[pos + 1..].into());
244 }
245 }
246 if token.text().contains('\n') {
247 break;
248 }
249 }
250 None
251}
252
34#[must_use] 253#[must_use]
35fn insert_children<N: AstNode>( 254fn insert_children<N: AstNode>(
36 parent: &N, 255 parent: &N,
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index 0433edb84..cefc00402 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -1,10 +1,8 @@
1//! Various extension methods to ast Nodes, which are hard to code-generate. 1//! Various extension methods to ast Nodes, which are hard to code-generate.
2//! Extensions for various expressions live in a sibling `expr_extensions` module. 2//! Extensions for various expressions live in a sibling `expr_extensions` module.
3 3
4use itertools::Itertools;
5
6use crate::{ 4use crate::{
7 ast::{self, child_opt, children, AstNode, SyntaxNode}, 5 ast::{self, child_opt, children, AstChildren, AstNode, AttrInput, SyntaxNode},
8 SmolStr, SyntaxElement, 6 SmolStr, SyntaxElement,
9 SyntaxKind::*, 7 SyntaxKind::*,
10 SyntaxToken, T, 8 SyntaxToken, T,
@@ -38,62 +36,37 @@ fn text_of_first_token(node: &SyntaxNode) -> &SmolStr {
38} 36}
39 37
40impl ast::Attr { 38impl ast::Attr {
41 pub fn is_inner(&self) -> bool { 39 pub fn as_simple_atom(&self) -> Option<SmolStr> {
42 let tt = match self.value() { 40 match self.input() {
43 None => return false, 41 None => self.simple_name(),
44 Some(tt) => tt, 42 Some(_) => None,
45 };
46
47 let prev = match tt.syntax().prev_sibling() {
48 None => return false,
49 Some(prev) => prev,
50 };
51
52 prev.kind() == T![!]
53 }
54
55 pub fn as_atom(&self) -> Option<SmolStr> {
56 let tt = self.value()?;
57 let (_bra, attr, _ket) = tt.syntax().children_with_tokens().collect_tuple()?;
58 if attr.kind() == IDENT {
59 Some(attr.as_token()?.text().clone())
60 } else {
61 None
62 } 43 }
63 } 44 }
64 45
65 pub fn as_call(&self) -> Option<(SmolStr, ast::TokenTree)> { 46 pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> {
66 let tt = self.value()?; 47 match self.input() {
67 let (_bra, attr, args, _ket) = tt.syntax().children_with_tokens().collect_tuple()?; 48 Some(AttrInput::TokenTree(tt)) => Some((self.simple_name()?, tt)),
68 let args = ast::TokenTree::cast(args.as_node()?.clone())?; 49 _ => None,
69 if attr.kind() == IDENT {
70 Some((attr.as_token()?.text().clone(), args))
71 } else {
72 None
73 } 50 }
74 } 51 }
75 52
76 pub fn as_named(&self) -> Option<SmolStr> { 53 pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> {
77 let tt = self.value()?; 54 match self.input() {
78 let attr = tt.syntax().children_with_tokens().nth(1)?; 55 Some(AttrInput::Literal(lit)) => {
79 if attr.kind() == IDENT { 56 let key = self.simple_name()?;
80 Some(attr.as_token()?.text().clone()) 57 // FIXME: escape? raw string?
81 } else { 58 let value = lit.syntax().first_token()?.text().trim_matches('"').into();
82 None 59 Some((key, value))
60 }
61 _ => None,
83 } 62 }
84 } 63 }
85 64
86 pub fn as_key_value(&self) -> Option<(SmolStr, SmolStr)> { 65 pub fn simple_name(&self) -> Option<SmolStr> {
87 let tt = self.value()?; 66 let path = self.path()?;
88 let tt_node = tt.syntax(); 67 match (path.segment(), path.qualifier()) {
89 let attr = tt_node.children_with_tokens().nth(1)?; 68 (Some(segment), None) => Some(segment.syntax().first_token()?.text().clone()),
90 if attr.kind() == IDENT { 69 _ => None,
91 let key = attr.as_token()?.text().clone();
92 let val_node = tt_node.children_with_tokens().find(|t| t.kind() == STRING)?;
93 let val = val_node.as_token()?.text().trim_start_matches('"').trim_end_matches('"');
94 Some((key, SmolStr::new(val)))
95 } else {
96 None
97 } 70 }
98 } 71 }
99} 72}
@@ -203,6 +176,16 @@ impl ast::ImplBlock {
203 } 176 }
204} 177}
205 178
179impl ast::AttrsOwner for ast::ImplItem {
180 fn attrs(&self) -> AstChildren<ast::Attr> {
181 match self {
182 ast::ImplItem::FnDef(it) => it.attrs(),
183 ast::ImplItem::TypeAliasDef(it) => it.attrs(),
184 ast::ImplItem::ConstDef(it) => it.attrs(),
185 }
186 }
187}
188
206#[derive(Debug, Clone, PartialEq, Eq)] 189#[derive(Debug, Clone, PartialEq, Eq)]
207pub enum StructKind { 190pub enum StructKind {
208 Tuple(ast::TupleFieldDefList), 191 Tuple(ast::TupleFieldDefList),
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index 1d20d8972..3bb5571ee 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -166,11 +166,52 @@ impl AstNode for Attr {
166 } 166 }
167} 167}
168impl Attr { 168impl Attr {
169 pub fn value(&self) -> Option<TokenTree> { 169 pub fn path(&self) -> Option<Path> {
170 AstChildren::new(&self.syntax).next()
171 }
172 pub fn input(&self) -> Option<AttrInput> {
170 AstChildren::new(&self.syntax).next() 173 AstChildren::new(&self.syntax).next()
171 } 174 }
172} 175}
173#[derive(Debug, Clone, PartialEq, Eq, Hash)] 176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
177pub enum AttrInput {
178 Literal(Literal),
179 TokenTree(TokenTree),
180}
181impl From<Literal> for AttrInput {
182 fn from(node: Literal) -> AttrInput {
183 AttrInput::Literal(node)
184 }
185}
186impl From<TokenTree> for AttrInput {
187 fn from(node: TokenTree) -> AttrInput {
188 AttrInput::TokenTree(node)
189 }
190}
191impl AstNode for AttrInput {
192 fn can_cast(kind: SyntaxKind) -> bool {
193 match kind {
194 LITERAL | TOKEN_TREE => true,
195 _ => false,
196 }
197 }
198 fn cast(syntax: SyntaxNode) -> Option<Self> {
199 let res = match syntax.kind() {
200 LITERAL => AttrInput::Literal(Literal { syntax }),
201 TOKEN_TREE => AttrInput::TokenTree(TokenTree { syntax }),
202 _ => return None,
203 };
204 Some(res)
205 }
206 fn syntax(&self) -> &SyntaxNode {
207 match self {
208 AttrInput::Literal(it) => &it.syntax,
209 AttrInput::TokenTree(it) => &it.syntax,
210 }
211 }
212}
213impl AttrInput {}
214#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub struct AwaitExpr { 215pub struct AwaitExpr {
175 pub(crate) syntax: SyntaxNode, 216 pub(crate) syntax: SyntaxNode,
176} 217}
diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs
index c3e676d4c..f275a4955 100644
--- a/crates/ra_syntax/src/ast/traits.rs
+++ b/crates/ra_syntax/src/ast/traits.rs
@@ -99,7 +99,7 @@ pub trait AttrsOwner: AstNode {
99 children(self) 99 children(self)
100 } 100 }
101 fn has_atom_attr(&self, atom: &str) -> bool { 101 fn has_atom_attr(&self, atom: &str) -> bool {
102 self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom) 102 self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom)
103 } 103 }
104} 104}
105 105
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 5f395501a..30328f59f 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -576,7 +576,8 @@ Grammar(
576 traits: [ "NameOwner", "AttrsOwner","DocCommentsOwner" ], 576 traits: [ "NameOwner", "AttrsOwner","DocCommentsOwner" ],
577 options: [ "TokenTree", "Path" ], 577 options: [ "TokenTree", "Path" ],
578 ), 578 ),
579 "Attr": ( options: [ ["value", "TokenTree"] ] ), 579 "AttrInput": ( enum: [ "Literal", "TokenTree" ] ),
580 "Attr": ( options: [ "Path", [ "input", "AttrInput" ] ] ),
580 "TokenTree": (), 581 "TokenTree": (),
581 "TypeParamList": ( 582 "TypeParamList": (
582 collections: [ 583 collections: [