aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--crates/ide_assists/src/handlers/add_missing_impl_members.rs3
-rw-r--r--crates/ide_assists/src/tests/generated.rs1
-rw-r--r--crates/ide_assists/src/utils.rs26
-rw-r--r--crates/syntax/src/ast/edit.rs151
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs72
-rw-r--r--crates/syntax/src/ast/make.rs2
7 files changed, 89 insertions, 168 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 498cf7d62..ba1be2e5e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,7 @@ incremental = false
9 9
10# Disabling debug info speeds up builds a bunch, 10# Disabling debug info speeds up builds a bunch,
11# and we don't rely on it for debugging that much. 11# and we don't rely on it for debugging that much.
12debug = 0 12debug = 1
13 13
14[profile.dev.package] 14[profile.dev.package]
15# These speed up local tests. 15# These speed up local tests.
diff --git a/crates/ide_assists/src/handlers/add_missing_impl_members.rs b/crates/ide_assists/src/handlers/add_missing_impl_members.rs
index 0148635f9..8225ae22c 100644
--- a/crates/ide_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ide_assists/src/handlers/add_missing_impl_members.rs
@@ -64,7 +64,6 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
64// impl Trait for () { 64// impl Trait for () {
65// type X = (); 65// type X = ();
66// fn foo(&self) {}$0 66// fn foo(&self) {}$0
67//
68// } 67// }
69// ``` 68// ```
70// -> 69// ->
@@ -195,6 +194,7 @@ impl Foo for S {
195 fn baz(&self) { 194 fn baz(&self) {
196 todo!() 195 todo!()
197 } 196 }
197
198}"#, 198}"#,
199 ); 199 );
200 } 200 }
@@ -231,6 +231,7 @@ impl Foo for S {
231 fn foo(&self) { 231 fn foo(&self) {
232 ${0:todo!()} 232 ${0:todo!()}
233 } 233 }
234
234}"#, 235}"#,
235 ); 236 );
236 } 237 }
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 49c1a9776..4406406a2 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -50,7 +50,6 @@ trait Trait {
50impl Trait for () { 50impl Trait for () {
51 type X = (); 51 type X = ();
52 fn foo(&self) {}$0 52 fn foo(&self) {}$0
53
54} 53}
55"#####, 54"#####,
56 r#####" 55 r#####"
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index 0dcf20c61..7d562d1d4 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -128,15 +128,12 @@ pub fn add_trait_assoc_items_to_impl(
128 sema: &hir::Semantics<ide_db::RootDatabase>, 128 sema: &hir::Semantics<ide_db::RootDatabase>,
129 items: Vec<ast::AssocItem>, 129 items: Vec<ast::AssocItem>,
130 trait_: hir::Trait, 130 trait_: hir::Trait,
131 impl_def: ast::Impl, 131 impl_: ast::Impl,
132 target_scope: hir::SemanticsScope, 132 target_scope: hir::SemanticsScope,
133) -> (ast::Impl, ast::AssocItem) { 133) -> (ast::Impl, ast::AssocItem) {
134 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
135
136 let n_existing_items = impl_item_list.assoc_items().count();
137 let source_scope = sema.scope_for_def(trait_); 134 let source_scope = sema.scope_for_def(trait_);
138 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 135 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
139 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); 136 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_.clone()));
140 137
141 let items = items 138 let items = items
142 .into_iter() 139 .into_iter()
@@ -147,13 +144,18 @@ pub fn add_trait_assoc_items_to_impl(
147 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), 144 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
148 _ => it, 145 _ => it,
149 }) 146 })
150 .map(|it| edit::remove_attrs_and_docs(&it)); 147 .map(|it| edit::remove_attrs_and_docs(&it).clone_subtree().clone_for_update());
151 148
152 let new_impl_item_list = impl_item_list.append_items(items); 149 let res = impl_.clone_for_update();
153 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); 150 let assoc_item_list = res.get_or_create_assoc_item_list();
154 let first_new_item = 151 let mut first_item = None;
155 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); 152 for item in items {
156 return (new_impl_def, first_new_item); 153 if first_item.is_none() {
154 first_item = Some(item.clone())
155 }
156 assoc_item_list.add_item(item)
157 }
158 return (res, first_item.unwrap());
157 159
158 fn add_body(fn_def: ast::Fn) -> ast::Fn { 160 fn add_body(fn_def: ast::Fn) -> ast::Fn {
159 match fn_def.body() { 161 match fn_def.body() {
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 10ec94cd2..7e4b8252e 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -80,81 +80,6 @@ where
80 } 80 }
81} 81}
82 82
83impl ast::Impl {
84 #[must_use]
85 pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl {
86 let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
87 if let Some(old_items) = self.assoc_item_list() {
88 let to_replace: SyntaxElement = old_items.syntax().clone().into();
89 to_insert.push(items.syntax().clone().into());
90 self.replace_children(single_node(to_replace), to_insert)
91 } else {
92 to_insert.push(make::tokens::single_space().into());
93 to_insert.push(items.syntax().clone().into());
94 self.insert_children(InsertPosition::Last, to_insert)
95 }
96 }
97}
98
99impl ast::AssocItemList {
100 #[must_use]
101 pub fn append_items(
102 &self,
103 items: impl IntoIterator<Item = ast::AssocItem>,
104 ) -> ast::AssocItemList {
105 let mut res = self.clone();
106 if !self.syntax().text().contains_char('\n') {
107 res = make_multiline(res);
108 }
109 items.into_iter().for_each(|it| res = res.append_item(it));
110 res.fixup_trailing_whitespace().unwrap_or(res)
111 }
112
113 #[must_use]
114 pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
115 let (indent, position, whitespace) = match self.assoc_items().last() {
116 Some(it) => (
117 leading_indent(it.syntax()).unwrap_or_default().to_string(),
118 InsertPosition::After(it.syntax().clone().into()),
119 "\n\n",
120 ),
121 None => match self.l_curly_token() {
122 Some(it) => (
123 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
124 InsertPosition::After(it.into()),
125 "\n",
126 ),
127 None => return self.clone(),
128 },
129 };
130 let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
131 let to_insert: ArrayVec<SyntaxElement, 2> =
132 [ws.ws().into(), item.syntax().clone().into()].into();
133 self.insert_children(position, to_insert)
134 }
135
136 /// Remove extra whitespace between last item and closing curly brace.
137 fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> {
138 let first_token_after_items =
139 self.assoc_items().last()?.syntax().next_sibling_or_token()?;
140 let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?;
141 if last_token_before_curly != first_token_after_items {
142 // there is something more between last item and
143 // right curly than just whitespace - bail out
144 return None;
145 }
146 let whitespace =
147 last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
148 let text = whitespace.syntax().text();
149 let newline = text.rfind('\n')?;
150 let keep = tokens::WsBuilder::new(&text[newline..]);
151 Some(self.replace_children(
152 first_token_after_items..=last_token_before_curly,
153 std::iter::once(keep.ws().into()),
154 ))
155 }
156}
157
158impl ast::RecordExprFieldList { 83impl ast::RecordExprFieldList {
159 #[must_use] 84 #[must_use]
160 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList { 85 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
@@ -246,21 +171,6 @@ impl ast::TypeAlias {
246 } 171 }
247} 172}
248 173
249impl ast::TypeParam {
250 #[must_use]
251 pub fn remove_bounds(&self) -> ast::TypeParam {
252 let colon = match self.colon_token() {
253 Some(it) => it,
254 None => return self.clone(),
255 };
256 let end = match self.type_bound_list() {
257 Some(it) => it.syntax().clone().into(),
258 None => colon.clone().into(),
259 };
260 self.replace_children(colon.into()..=end, iter::empty())
261 }
262}
263
264impl ast::Path { 174impl ast::Path {
265 #[must_use] 175 #[must_use]
266 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { 176 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
@@ -411,61 +321,6 @@ impl ast::MatchArmList {
411 } 321 }
412} 322}
413 323
414impl ast::GenericParamList {
415 #[must_use]
416 pub fn append_params(
417 &self,
418 params: impl IntoIterator<Item = ast::GenericParam>,
419 ) -> ast::GenericParamList {
420 let mut res = self.clone();
421 params.into_iter().for_each(|it| res = res.append_param(it));
422 res
423 }
424
425 #[must_use]
426 pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
427 let space = tokens::single_space();
428
429 let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
430 if self.generic_params().next().is_some() {
431 to_insert.push(space.into());
432 }
433 to_insert.push(item.syntax().clone().into());
434
435 macro_rules! after_l_angle {
436 () => {{
437 let anchor = match self.l_angle_token() {
438 Some(it) => it.into(),
439 None => return self.clone(),
440 };
441 InsertPosition::After(anchor)
442 }};
443 }
444
445 macro_rules! after_field {
446 ($anchor:expr) => {
447 if let Some(comma) = $anchor
448 .syntax()
449 .siblings_with_tokens(Direction::Next)
450 .find(|it| it.kind() == T![,])
451 {
452 InsertPosition::After(comma)
453 } else {
454 to_insert.insert(0, make::token(T![,]).into());
455 InsertPosition::After($anchor.syntax().clone().into())
456 }
457 };
458 }
459
460 let position = match self.generic_params().last() {
461 Some(it) => after_field!(it),
462 None => after_l_angle!(),
463 };
464
465 self.insert_children(position, to_insert)
466 }
467}
468
469#[must_use] 324#[must_use]
470pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 325pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
471 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() 326 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
@@ -516,6 +371,12 @@ impl ops::Add<u8> for IndentLevel {
516} 371}
517 372
518impl IndentLevel { 373impl IndentLevel {
374 pub fn single() -> IndentLevel {
375 IndentLevel(0)
376 }
377 pub fn is_zero(&self) -> bool {
378 self.0 == 0
379 }
519 pub fn from_element(element: &SyntaxElement) -> IndentLevel { 380 pub fn from_element(element: &SyntaxElement) -> IndentLevel {
520 match element { 381 match element {
521 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it), 382 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 168355555..9812e00c9 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -2,11 +2,16 @@
2 2
3use std::iter::empty; 3use std::iter::empty;
4 4
5use parser::T; 5use parser::{SyntaxKind, T};
6use rowan::SyntaxElement;
6 7
7use crate::{ 8use crate::{
8 algo::neighbor, 9 algo::neighbor,
9 ast::{self, edit::AstNodeEdit, make, GenericParamsOwner, WhereClause}, 10 ast::{
11 self,
12 edit::{AstNodeEdit, IndentLevel},
13 make, GenericParamsOwner,
14 },
10 ted::{self, Position}, 15 ted::{self, Position},
11 AstNode, AstToken, Direction, 16 AstNode, AstToken, Direction,
12}; 17};
@@ -37,7 +42,7 @@ impl GenericParamsOwnerEdit for ast::Fn {
37 } 42 }
38 } 43 }
39 44
40 fn get_or_create_where_clause(&self) -> WhereClause { 45 fn get_or_create_where_clause(&self) -> ast::WhereClause {
41 if self.where_clause().is_none() { 46 if self.where_clause().is_none() {
42 let position = if let Some(ty) = self.ret_type() { 47 let position = if let Some(ty) = self.ret_type() {
43 Position::after(ty.syntax()) 48 Position::after(ty.syntax())
@@ -67,7 +72,7 @@ impl GenericParamsOwnerEdit for ast::Impl {
67 } 72 }
68 } 73 }
69 74
70 fn get_or_create_where_clause(&self) -> WhereClause { 75 fn get_or_create_where_clause(&self) -> ast::WhereClause {
71 if self.where_clause().is_none() { 76 if self.where_clause().is_none() {
72 let position = if let Some(items) = self.assoc_item_list() { 77 let position = if let Some(items) = self.assoc_item_list() {
73 Position::before(items.syntax()) 78 Position::before(items.syntax())
@@ -97,7 +102,7 @@ impl GenericParamsOwnerEdit for ast::Trait {
97 } 102 }
98 } 103 }
99 104
100 fn get_or_create_where_clause(&self) -> WhereClause { 105 fn get_or_create_where_clause(&self) -> ast::WhereClause {
101 if self.where_clause().is_none() { 106 if self.where_clause().is_none() {
102 let position = if let Some(items) = self.assoc_item_list() { 107 let position = if let Some(items) = self.assoc_item_list() {
103 Position::before(items.syntax()) 108 Position::before(items.syntax())
@@ -127,7 +132,7 @@ impl GenericParamsOwnerEdit for ast::Struct {
127 } 132 }
128 } 133 }
129 134
130 fn get_or_create_where_clause(&self) -> WhereClause { 135 fn get_or_create_where_clause(&self) -> ast::WhereClause {
131 if self.where_clause().is_none() { 136 if self.where_clause().is_none() {
132 let tfl = self.field_list().and_then(|fl| match fl { 137 let tfl = self.field_list().and_then(|fl| match fl {
133 ast::FieldList::RecordFieldList(_) => None, 138 ast::FieldList::RecordFieldList(_) => None,
@@ -165,7 +170,7 @@ impl GenericParamsOwnerEdit for ast::Enum {
165 } 170 }
166 } 171 }
167 172
168 fn get_or_create_where_clause(&self) -> WhereClause { 173 fn get_or_create_where_clause(&self) -> ast::WhereClause {
169 if self.where_clause().is_none() { 174 if self.where_clause().is_none() {
170 let position = if let Some(gpl) = self.generic_param_list() { 175 let position = if let Some(gpl) = self.generic_param_list() {
171 Position::after(gpl.syntax()) 176 Position::after(gpl.syntax())
@@ -272,6 +277,59 @@ impl ast::Use {
272 } 277 }
273} 278}
274 279
280impl ast::Impl {
281 pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList {
282 if self.assoc_item_list().is_none() {
283 let assoc_item_list = make::assoc_item_list().clone_for_update();
284 ted::append_child(self.syntax(), assoc_item_list.syntax());
285 }
286 self.assoc_item_list().unwrap()
287 }
288}
289
290impl ast::AssocItemList {
291 pub fn add_item(&self, item: ast::AssocItem) {
292 let (indent, position, whitespace) = match self.assoc_items().last() {
293 Some(last_item) => (
294 IndentLevel::from_node(last_item.syntax()),
295 Position::after(last_item.syntax()),
296 "\n\n",
297 ),
298 None => match self.l_curly_token() {
299 Some(l_curly) => {
300 self.normalize_ws_between_braces();
301 (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
302 }
303 None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
304 },
305 };
306 let elements: Vec<SyntaxElement<_>> = vec![
307 make::tokens::whitespace(&format!("{}{}", whitespace, indent)).into(),
308 item.syntax().clone().into(),
309 ];
310 ted::insert_all(position, elements);
311 }
312
313 fn normalize_ws_between_braces(&self) -> Option<()> {
314 let l = self.l_curly_token()?;
315 let r = self.r_curly_token()?;
316 let indent = IndentLevel::from_node(self.syntax());
317
318 match l.next_sibling_or_token() {
319 Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => {
320 if ws.next_sibling_or_token()?.into_token()? == r {
321 ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent)));
322 }
323 }
324 Some(ws) if ws.kind() == T!['}'] => {
325 ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent)));
326 }
327 _ => (),
328 }
329 Some(())
330 }
331}
332
275#[cfg(test)] 333#[cfg(test)]
276mod tests { 334mod tests {
277 use std::fmt; 335 use std::fmt;
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index de04c8620..d13926ded 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -99,7 +99,7 @@ fn ty_from_text(text: &str) -> ast::Type {
99} 99}
100 100
101pub fn assoc_item_list() -> ast::AssocItemList { 101pub fn assoc_item_list() -> ast::AssocItemList {
102 ast_from_text("impl C for D {};") 102 ast_from_text("impl C for D {}")
103} 103}
104 104
105pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl { 105pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {