fix: Insert whitespace into trait-impl completions when coming from macros

This commit is contained in:
Lukas Wirth 2022-05-24 22:33:42 +02:00
parent 6f006b7524
commit 86d1d9067e
6 changed files with 99 additions and 83 deletions

View file

@ -939,7 +939,6 @@ struct Foo(usize);
impl FooB for Foo { impl FooB for Foo {
$0fn foo< 'lt>(& 'lt self){} $0fn foo< 'lt>(& 'lt self){}
} }
"#, "#,
) )

View file

@ -32,10 +32,12 @@
//! ``` //! ```
use hir::{self, HasAttrs}; use hir::{self, HasAttrs};
use ide_db::{path_transform::PathTransform, traits::get_missing_assoc_items, SymbolKind}; use ide_db::{
path_transform::PathTransform, syntax_helpers::insert_whitespace_into_node,
traits::get_missing_assoc_items, SymbolKind,
};
use syntax::{ use syntax::{
ast::{self, edit_in_place::AttrsOwnerEdit}, ast::{self, edit_in_place::AttrsOwnerEdit},
display::function_declaration,
AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T,
}; };
use text_edit::TextEdit; use text_edit::TextEdit;
@ -179,7 +181,7 @@ fn add_function_impl(
_ => unreachable!(), _ => unreachable!(),
}; };
let function_decl = function_declaration(&transformed_fn); let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
match ctx.config.snippet_cap { match ctx.config.snippet_cap {
Some(cap) => { Some(cap) => {
let snippet = format!("{} {{\n $0\n}}", function_decl); let snippet = format!("{} {{\n $0\n}}", function_decl);
@ -260,7 +262,7 @@ fn add_const_impl(
_ => unreachable!(), _ => unreachable!(),
}; };
let label = make_const_compl_syntax(&transformed_const); let label = make_const_compl_syntax(&transformed_const, source.file_id.is_macro());
let replacement = format!("{} ", label); let replacement = format!("{} ", label);
let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label); let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label);
@ -283,17 +285,18 @@ fn add_const_impl(
} }
} }
fn make_const_compl_syntax(const_: &ast::Const) -> String { fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> String {
const_.remove_attrs_and_docs(); const_.remove_attrs_and_docs();
let const_ = if needs_whitespace {
insert_whitespace_into_node::insert_ws_into(const_.syntax().clone())
} else {
const_.syntax().clone()
};
let const_start = const_.syntax().text_range().start(); let start = const_.text_range().start();
let const_end = const_.syntax().text_range().end(); let const_end = const_.text_range().end();
let start =
const_.syntax().first_child_or_token().map_or(const_start, |f| f.text_range().start());
let end = const_ let end = const_
.syntax()
.children_with_tokens() .children_with_tokens()
.find(|s| s.kind() == T![;] || s.kind() == T![=]) .find(|s| s.kind() == T![;] || s.kind() == T![=])
.map_or(const_end, |f| f.text_range().start()); .map_or(const_end, |f| f.text_range().start());
@ -301,11 +304,36 @@ fn make_const_compl_syntax(const_: &ast::Const) -> String {
let len = end - start; let len = end - start;
let range = TextRange::new(0.into(), len); let range = TextRange::new(0.into(), len);
let syntax = const_.syntax().text().slice(range).to_string(); let syntax = const_.text().slice(range).to_string();
format!("{} =", syntax.trim_end()) format!("{} =", syntax.trim_end())
} }
fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
node.remove_attrs_and_docs();
let node = if needs_whitespace {
insert_whitespace_into_node::insert_ws_into(node.syntax().clone())
} else {
node.syntax().clone()
};
let start = node.text_range().start();
let end = node.text_range().end();
let end = node
.last_child_or_token()
.filter(|s| s.kind() == T![;] || s.kind() == SyntaxKind::BLOCK_EXPR)
.map_or(end, |f| f.text_range().start());
let len = end - start;
let range = TextRange::new(0.into(), len);
let syntax = node.text().slice(range).to_string();
syntax.trim_end().to_owned()
}
fn replacement_range(ctx: &CompletionContext, item: &SyntaxNode) -> TextRange { fn replacement_range(ctx: &CompletionContext, item: &SyntaxNode) -> TextRange {
let first_child = item let first_child = item
.children_with_tokens() .children_with_tokens()
@ -655,8 +683,7 @@ trait Test {
struct T; struct T;
impl Test for T { impl Test for T {
fn foo<T>() fn foo<T>() where T: Into<String> {
where T: Into<String> {
$0 $0
} }
} }
@ -1052,4 +1079,51 @@ impl Tr for () {
"#]], "#]],
); );
} }
#[test]
fn fixes_up_macro_generated() {
check_edit(
"fn foo",
r#"
macro_rules! noop {
($($item: item)*) => {
$($item)*
}
}
noop! {
trait Foo {
fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32>;
}
}
struct Test;
impl Foo for Test {
$0
}
"#,
r#"
macro_rules! noop {
($($item: item)*) => {
$($item)*
}
}
noop! {
trait Foo {
fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32>;
}
}
struct Test;
impl Foo for Test {
fn foo(&mut self,bar:i64,baz: &mut u32) -> Result<(),u32> {
$0
}
}
"#,
);
}
} }

View file

@ -114,6 +114,10 @@ pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
ted::insert(pos, insert); ted::insert(pos, insert);
} }
if let Some(it) = syn.last_token().filter(|it| it.kind() == SyntaxKind::WHITESPACE) {
ted::remove(it);
}
syn syn
} }

View file

@ -250,8 +250,7 @@ fn main() {
"#, "#,
expect![[r#" expect![[r#"
bar bar
for _ in 0..42{} for _ in 0..42{}"#]],
"#]],
); );
} }
@ -273,7 +272,6 @@ f$0oo!();
expect![[r#" expect![[r#"
foo foo
fn b(){} fn b(){}
"#]], "#]],
); );
} }
@ -297,8 +295,7 @@ f$0oo!();
fn some_thing() -> u32 { fn some_thing() -> u32 {
let a = 0; let a = 0;
a+10 a+10
} }"#]],
"#]],
); );
} }
@ -359,8 +356,7 @@ fn main() {
"#, "#,
expect![[r#" expect![[r#"
match_ast match_ast
{} {}"#]],
"#]],
); );
} }
@ -421,8 +417,7 @@ fn main() {
"#, "#,
expect![[r#" expect![[r#"
foo foo
fn f<T>(_: &dyn ::std::marker::Copy){} fn f<T>(_: &dyn ::std::marker::Copy){}"#]],
"#]],
); );
} }
@ -440,7 +435,6 @@ struct Foo {}
expect![[r#" expect![[r#"
Clone Clone
impl < >core::clone::Clone for Foo< >{} impl < >core::clone::Clone for Foo< >{}
"#]], "#]],
); );
} }
@ -458,7 +452,6 @@ struct Foo {}
expect![[r#" expect![[r#"
Copy Copy
impl < >core::marker::Copy for Foo< >{} impl < >core::marker::Copy for Foo< >{}
"#]], "#]],
); );
} }
@ -475,7 +468,6 @@ struct Foo {}
expect![[r#" expect![[r#"
Copy Copy
impl < >core::marker::Copy for Foo< >{} impl < >core::marker::Copy for Foo< >{}
"#]], "#]],
); );
check( check(
@ -488,7 +480,6 @@ struct Foo {}
expect![[r#" expect![[r#"
Clone Clone
impl < >core::clone::Clone for Foo< >{} impl < >core::clone::Clone for Foo< >{}
"#]], "#]],
); );
} }

View file

@ -1,51 +0,0 @@
//! This module contains utilities for rendering syntax nodes into a string representing their signature.
use crate::ast::{self, HasGenericParams, HasName};
use ast::HasVisibility;
use stdx::format_to;
pub fn function_declaration(node: &ast::Fn) -> String {
let mut buf = String::new();
if let Some(vis) = node.visibility() {
format_to!(buf, "{} ", vis);
}
if node.async_token().is_some() {
format_to!(buf, "async ");
}
if node.const_token().is_some() {
format_to!(buf, "const ");
}
if node.unsafe_token().is_some() {
format_to!(buf, "unsafe ");
}
if let Some(abi) = node.abi() {
// Keyword `extern` is included in the string.
format_to!(buf, "{} ", abi);
}
if let Some(name) = node.name() {
format_to!(buf, "fn {}", name);
}
if let Some(type_params) = node.generic_param_list() {
format_to!(buf, "{}", type_params);
}
if let Some(param_list) = node.param_list() {
let params: Vec<String> = param_list
.self_param()
.into_iter()
.map(|self_param| self_param.to_string())
.chain(param_list.params().map(|param| param.to_string()))
.collect();
// Useful to inline parameters
format_to!(buf, "({})", params.join(", "));
}
if let Some(ret_type) = node.ret_type() {
if ret_type.ty().is_some() {
format_to!(buf, " {}", ret_type);
}
}
if let Some(where_clause) = node.where_clause() {
format_to!(buf, "\n{}", where_clause);
}
buf
}

View file

@ -33,7 +33,6 @@ mod token_text;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub mod display;
pub mod algo; pub mod algo;
pub mod ast; pub mod ast;
#[doc(hidden)] #[doc(hidden)]