From 4a3a525ea93cc5761e03aa066f6703dc9563bcaa Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 19 May 2020 21:39:27 +0200 Subject: [PATCH 1/6] Use new format for all assists that don't change cursor positon --- crates/rust-analyzer/src/to_proto.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 2b1a3378f8a..af54f81b7da 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -639,7 +639,7 @@ fn main() { } pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result { - let res = if assist.source_change.is_snippet { + let res = if assist.source_change.cursor_position.is_none() { lsp_ext::CodeAction { title: assist.label, kind: Some(String::new()), @@ -647,6 +647,7 @@ pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result Date: Tue, 19 May 2020 21:40:47 +0200 Subject: [PATCH 2/6] Cleanup --- crates/ra_assists/src/handlers/add_explicit_type.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index 0c7d5e355f7..770049212d2 100644 --- a/crates/ra_assists/src/handlers/add_explicit_type.rs +++ b/crates/ra_assists/src/handlers/add_explicit_type.rs @@ -25,9 +25,8 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio let stmt = ctx.find_node_at_offset::()?; let module = ctx.sema.scope(stmt.syntax()).module()?; let expr = stmt.initializer()?; - let pat = stmt.pat()?; // Must be a binding - let pat = match pat { + let pat = match stmt.pat()? { ast::Pat::BindPat(bind_pat) => bind_pat, _ => return None, }; @@ -46,7 +45,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio // Assist not applicable if the type has already been specified // and it has no placeholders let ascribed_ty = stmt.ascribed_type(); - if let Some(ref ty) = ascribed_ty { + if let Some(ty) = &ascribed_ty { if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { return None; } From 9c3acd3028d32b6cf099e8d5dffe435f15f241a2 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 19 May 2020 21:43:14 +0200 Subject: [PATCH 3/6] Cleanup --- crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 6a49b7dbd18..eb57b7231fa 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs @@ -1,6 +1,5 @@ use ra_ide_db::RootDatabase; use ra_syntax::ast::{self, AstNode, NameOwner}; -use stdx::format_to; use test_utils::tested_by; use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; @@ -35,7 +34,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> } let field_type = field_list.fields().next()?.type_ref()?; let path = match field_type { - ast::TypeRef::PathType(p) => p, + ast::TypeRef::PathType(it) => it, _ => return None, }; @@ -51,9 +50,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> target, |edit| { let start_offset = variant.parent_enum().syntax().text_range().end(); - let mut buf = String::new(); - format_to!( - buf, + let buf = format!( r#" impl From<{0}> for {1} {{ From 8eb3272ad6f774bccb967ee640b72a9a17273e7b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 19 May 2020 22:25:07 +0200 Subject: [PATCH 4/6] Use snippets in add function --- .../ra_assists/src/handlers/add_function.rs | 79 ++++++++++--------- crates/ra_assists/src/tests/generated.rs | 2 +- crates/ra_assists/src/utils.rs | 24 +++++- crates/stdx/src/lib.rs | 8 ++ docs/user/assists.md | 2 +- 5 files changed, 75 insertions(+), 40 deletions(-) diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index de016ae4e15..69fede00fb1 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs @@ -10,7 +10,7 @@ }; use rustc_hash::{FxHashMap, FxHashSet}; -use crate::{AssistContext, AssistId, Assists}; +use crate::{utils::render_snippet, AssistContext, AssistId, Assists}; // Assist: add_function // @@ -33,7 +33,7 @@ // } // // fn bar(arg: &str, baz: Baz) { -// todo!() +// ${0:todo!()} // } // // ``` @@ -58,18 +58,27 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; let target = call.syntax().text_range(); - acc.add(AssistId("add_function"), "Add function", target, |edit| { + acc.add(AssistId("add_function"), "Add function", target, |builder| { let function_template = function_builder.render(); - edit.set_file(function_template.file); - edit.set_cursor(function_template.cursor_offset); - edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); + builder.set_file(function_template.file); + match ctx.config.snippet_cap { + Some(cap) => { + let snippet = render_snippet( + function_template.fn_def.syntax(), + function_template.placeholder_expr.syntax(), + ); + builder.insert_snippet(cap, function_template.insert_offset, snippet) + } + None => builder + .insert(function_template.insert_offset, function_template.fn_def.to_string()), + } }) } struct FunctionTemplate { insert_offset: TextSize, - cursor_offset: TextSize, fn_def: ast::SourceFile, + placeholder_expr: ast::MacroCall, file: FileId, } @@ -136,9 +145,7 @@ fn render(self) -> FunctionTemplate { let placeholder_expr = fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); - let cursor_offset_from_fn_start = placeholder_expr.syntax().text_range().start(); - let cursor_offset = insert_offset + cursor_offset_from_fn_start; - FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file } + FunctionTemplate { insert_offset, placeholder_expr, fn_def, file: self.file } } } @@ -316,7 +323,7 @@ fn foo() { } fn bar() { - <|>todo!() + ${0:todo!()} } ", ) @@ -343,7 +350,7 @@ fn foo() { } fn bar() { - <|>todo!() + ${0:todo!()} } ", ) @@ -367,7 +374,7 @@ fn foo1() { } fn bar() { - <|>todo!() + ${0:todo!()} } fn foo2() {} @@ -393,7 +400,7 @@ fn foo() { } fn bar() { - <|>todo!() + ${0:todo!()} } } ", @@ -419,7 +426,7 @@ fn foo() { } fn bar(baz: Baz) { - <|>todo!() + ${0:todo!()} } ", ); @@ -452,7 +459,7 @@ fn baz(&self) -> Baz { } fn bar(baz: Baz) { - <|>todo!() + ${0:todo!()} } ", ) @@ -473,7 +480,7 @@ fn foo() { } fn bar(arg: &str) { - <|>todo!() + ${0:todo!()} } "#, ) @@ -494,7 +501,7 @@ fn foo() { } fn bar(arg: char) { - <|>todo!() + ${0:todo!()} } "#, ) @@ -515,7 +522,7 @@ fn foo() { } fn bar(arg: i32) { - <|>todo!() + ${0:todo!()} } ", ) @@ -536,7 +543,7 @@ fn foo() { } fn bar(arg: u8) { - <|>todo!() + ${0:todo!()} } ", ) @@ -561,7 +568,7 @@ fn foo() { } fn bar(x: u8) { - <|>todo!() + ${0:todo!()} } ", ) @@ -584,7 +591,7 @@ fn foo() { } fn bar(worble: ()) { - <|>todo!() + ${0:todo!()} } ", ) @@ -613,7 +620,7 @@ fn baz() { } fn bar(foo: impl Foo) { - <|>todo!() + ${0:todo!()} } ", ) @@ -640,7 +647,7 @@ fn foo() { } fn bar(baz: &Baz) { - <|>todo!() + ${0:todo!()} } ", ) @@ -669,7 +676,7 @@ fn foo() { } fn bar(baz: Baz::Bof) { - <|>todo!() + ${0:todo!()} } ", ) @@ -692,7 +699,7 @@ fn foo(t: T) { } fn bar(t: T) { - <|>todo!() + ${0:todo!()} } ", ) @@ -723,7 +730,7 @@ fn foo() { } fn bar(arg: fn() -> Baz) { - <|>todo!() + ${0:todo!()} } ", ) @@ -748,7 +755,7 @@ fn foo() { } fn bar(closure: impl Fn(i64) -> i64) { - <|>todo!() + ${0:todo!()} } ", ) @@ -769,7 +776,7 @@ fn foo() { } fn bar(baz: ()) { - <|>todo!() + ${0:todo!()} } ", ) @@ -794,7 +801,7 @@ fn foo() { } fn bar(baz_1: Baz, baz_2: Baz) { - <|>todo!() + ${0:todo!()} } ", ) @@ -819,7 +826,7 @@ fn foo() { } fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { - <|>todo!() + ${0:todo!()} } "#, ) @@ -839,7 +846,7 @@ fn foo() { r" mod bar { pub(crate) fn my_fn() { - <|>todo!() + ${0:todo!()} } } @@ -878,7 +885,7 @@ fn bar() { } fn baz(foo: foo::Foo) { - <|>todo!() + ${0:todo!()} } ", ) @@ -902,7 +909,7 @@ mod bar { fn something_else() {} pub(crate) fn my_fn() { - <|>todo!() + ${0:todo!()} } } @@ -930,7 +937,7 @@ fn foo() { mod bar { mod baz { pub(crate) fn my_fn() { - <|>todo!() + ${0:todo!()} } } } @@ -959,7 +966,7 @@ fn main() { pub(crate) fn bar() { - <|>todo!() + ${0:todo!()} }", ) } diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 32fbcdef4ab..1d82c245d04 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -78,7 +78,7 @@ fn foo() { } fn bar(arg: &str, baz: Baz) { - todo!() + ${0:todo!()} } "#####, diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index f3fc92ebf26..bb9749b06f2 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs @@ -1,18 +1,38 @@ //! Assorted functions shared by several assists. pub(crate) mod insert_use; -use std::iter; +use std::{iter, ops}; use hir::{Adt, Crate, Semantics, Trait, Type}; use ra_ide_db::RootDatabase; use ra_syntax::{ ast::{self, make, NameOwner}, - AstNode, T, + AstNode, SyntaxNode, T, }; use rustc_hash::FxHashSet; pub(crate) use insert_use::insert_use_statement; +pub(crate) fn render_snippet(node: &SyntaxNode, placeholder: &SyntaxNode) -> String { + assert!(placeholder.ancestors().any(|it| it == *node)); + let range = placeholder.text_range() - node.text_range().start(); + let range: ops::Range = range.into(); + + let mut placeholder = placeholder.to_string(); + escape(&mut placeholder); + let tab_stop = format!("${{0:{}}}", placeholder); + + let mut buf = node.to_string(); + buf.replace_range(range, &tab_stop); + return buf; + + fn escape(buf: &mut String) { + stdx::replace(buf, '{', r"\{"); + stdx::replace(buf, '}', r"\}"); + stdx::replace(buf, '$', r"\$"); + } +} + pub fn get_missing_assoc_items( sema: &Semantics, impl_def: &ast::ImplDef, diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 0f34ce70e1a..71a57fba230 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs @@ -116,3 +116,11 @@ pub fn to_lower_snake_case(s: &str) -> String { } buf } + +pub fn replace(buf: &mut String, from: char, to: &str) { + if !buf.contains(from) { + return; + } + // FIXME: do this in place. + *buf = buf.replace(from, to) +} diff --git a/docs/user/assists.md b/docs/user/assists.md index c72b50a4dab..10ab67b2e72 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md @@ -77,7 +77,7 @@ fn foo() { } fn bar(arg: &str, baz: Baz) { - todo!() + ${0:todo!()} } ``` From 4de2749db8281c00aba37270fa9ae8d4bd2572d8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 20 May 2020 01:28:11 +0200 Subject: [PATCH 5/6] Explain the purpose of `ast::make` module more clearly --- .../src/handlers/replace_unwrap_with_match.rs | 2 +- crates/ra_syntax/src/ast/make.rs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs index c4b56f6e90a..b379b55a83f 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs @@ -51,7 +51,7 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); - let unreachable_call = make::unreachable_macro_call().into(); + let unreachable_call = make::expr_unreachable(); let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index d0e960fb497..2db01703888 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs @@ -1,5 +1,9 @@ //! This module contains free-standing functions for creating AST fragments out //! of smaller pieces. +//! +//! Note that all functions here intended to be stupid constructors, which just +//! assemble a finish node from immediate children. If you want to do something +//! smarter than that, it probably doesn't belong in this module. use itertools::Itertools; use stdx::format_to; @@ -95,6 +99,9 @@ pub fn expr_empty_block() -> ast::Expr { pub fn expr_unimplemented() -> ast::Expr { expr_from_text("unimplemented!()") } +pub fn expr_unreachable() -> ast::Expr { + expr_from_text("unreachable!()") +} pub fn expr_todo() -> ast::Expr { expr_from_text("todo!()") } @@ -264,10 +271,6 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken { .unwrap_or_else(|| panic!("unhandled token: {:?}", kind)) } -pub fn unreachable_macro_call() -> ast::MacroCall { - ast_from_text(&format!("unreachable!()")) -} - pub fn param(name: String, ty: String) -> ast::Param { ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty)) } From e6fc0bdffb213f6e94c5bb4081e6d175ccbd518f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 19 May 2020 23:12:01 +0200 Subject: [PATCH 6/6] Moderate cleanup of add_function --- .../ra_assists/src/handlers/add_function.rs | 78 +++++++++++-------- crates/ra_assists/src/utils.rs | 8 +- crates/ra_syntax/src/algo.rs | 36 +++++++-- crates/ra_syntax/src/ast/edit.rs | 44 +++++++++-- crates/ra_syntax/src/ast/make.rs | 25 +++--- 5 files changed, 127 insertions(+), 64 deletions(-) diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index 69fede00fb1..a0709630d27 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs @@ -4,13 +4,13 @@ ast::{ self, edit::{AstNodeEdit, IndentLevel}, - ArgListOwner, AstNode, ModuleItemOwner, + make, ArgListOwner, AstNode, ModuleItemOwner, }, SyntaxKind, SyntaxNode, TextSize, }; use rustc_hash::{FxHashMap, FxHashSet}; -use crate::{utils::render_snippet, AssistContext, AssistId, Assists}; +use crate::{assist_config::SnippetCap, utils::render_snippet, AssistContext, AssistId, Assists}; // Assist: add_function // @@ -61,27 +61,33 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> acc.add(AssistId("add_function"), "Add function", target, |builder| { let function_template = function_builder.render(); builder.set_file(function_template.file); + let new_fn = function_template.to_string(ctx.config.snippet_cap); match ctx.config.snippet_cap { - Some(cap) => { - let snippet = render_snippet( - function_template.fn_def.syntax(), - function_template.placeholder_expr.syntax(), - ); - builder.insert_snippet(cap, function_template.insert_offset, snippet) - } - None => builder - .insert(function_template.insert_offset, function_template.fn_def.to_string()), + Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), + None => builder.insert(function_template.insert_offset, new_fn), } }) } struct FunctionTemplate { insert_offset: TextSize, - fn_def: ast::SourceFile, placeholder_expr: ast::MacroCall, + leading_ws: String, + fn_def: ast::FnDef, + trailing_ws: String, file: FileId, } +impl FunctionTemplate { + fn to_string(&self, cap: Option) -> String { + let f = match cap { + Some(cap) => render_snippet(cap, self.fn_def.syntax(), self.placeholder_expr.syntax()), + None => self.fn_def.to_string(), + }; + format!("{}{}{}", self.leading_ws, f, self.trailing_ws) + } +} + struct FunctionBuilder { target: GeneratedFunctionTarget, fn_name: ast::Name, @@ -119,33 +125,41 @@ fn from_call( } fn render(self) -> FunctionTemplate { - let placeholder_expr = ast::make::expr_todo(); - let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); - let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); - if self.needs_pub { - fn_def = ast::make::add_pub_crate_modifier(fn_def); - } + let placeholder_expr = make::expr_todo(); + let fn_body = make::block_expr(vec![], Some(placeholder_expr)); + let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; + let mut fn_def = + make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body); + let leading_ws; + let trailing_ws; - let (fn_def, insert_offset) = match self.target { + let insert_offset = match self.target { GeneratedFunctionTarget::BehindItem(it) => { - let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); - let indented = with_leading_blank_line.indent(IndentLevel::from_node(&it)); - (indented, it.text_range().end()) + let indent = IndentLevel::from_node(&it); + leading_ws = format!("\n\n{}", indent); + fn_def = fn_def.indent(indent); + trailing_ws = String::new(); + it.text_range().end() } GeneratedFunctionTarget::InEmptyItemList(it) => { - let indent_once = IndentLevel(1); let indent = IndentLevel::from_node(it.syntax()); - let fn_def = ast::make::add_leading_newlines(1, fn_def); - let fn_def = fn_def.indent(indent_once); - let fn_def = ast::make::add_trailing_newlines(1, fn_def); - let fn_def = fn_def.indent(indent); - (fn_def, it.syntax().text_range().start() + TextSize::of('{')) + leading_ws = format!("\n{}", indent + 1); + fn_def = fn_def.indent(indent + 1); + trailing_ws = format!("\n{}", indent); + it.syntax().text_range().start() + TextSize::of('{') } }; let placeholder_expr = fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); - FunctionTemplate { insert_offset, placeholder_expr, fn_def, file: self.file } + FunctionTemplate { + insert_offset, + placeholder_expr, + leading_ws, + fn_def, + trailing_ws, + file: self.file, + } } } @@ -165,7 +179,7 @@ fn syntax(&self) -> &SyntaxNode { fn fn_name(call: &ast::Path) -> Option { let name = call.segment()?.syntax().to_string(); - Some(ast::make::name(&name)) + Some(make::name(&name)) } /// Computes the type variables and arguments required for the generated function @@ -187,8 +201,8 @@ fn fn_args( }); } deduplicate_arg_names(&mut arg_names); - let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty)); - Some((None, ast::make::param_list(params))) + let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty)); + Some((None, make::param_list(params))) } /// Makes duplicate argument names unique by appending incrementing numbers. diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index bb9749b06f2..8a26a680828 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs @@ -11,9 +11,15 @@ }; use rustc_hash::FxHashSet; +use crate::assist_config::SnippetCap; + pub(crate) use insert_use::insert_use_statement; -pub(crate) fn render_snippet(node: &SyntaxNode, placeholder: &SyntaxNode) -> String { +pub(crate) fn render_snippet( + _cap: SnippetCap, + node: &SyntaxNode, + placeholder: &SyntaxNode, +) -> String { assert!(placeholder.ancestors().any(|it| it == *node)); let range = placeholder.text_range() - node.text_range().start(); let range: ops::Range = range.into(); diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 2a8dac757b6..664894d1f83 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs @@ -266,6 +266,15 @@ pub fn replace>(&mut self, what: &T, with: &T) { let replacement = Replacement::Single(with.clone().into()); self.replacements.insert(what, replacement); } + pub fn replace_with_many>( + &mut self, + what: &T, + with: Vec, + ) { + let what = what.clone().into(); + let replacement = Replacement::Many(with); + self.replacements.insert(what, replacement); + } pub fn replace_ast(&mut self, what: &T, with: &T) { self.replace(what.syntax(), with.syntax()) } @@ -302,31 +311,41 @@ fn replacement(&self, element: &SyntaxElement) -> Option { fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode { // FIXME: this could be made much faster. - let new_children = - node.children_with_tokens().flat_map(|it| self.rewrite_self(&it)).collect::>(); + let mut new_children = Vec::new(); + for child in node.children_with_tokens() { + self.rewrite_self(&mut new_children, &child); + } with_children(node, new_children) } fn rewrite_self( &self, + acc: &mut Vec>, element: &SyntaxElement, - ) -> Option> { + ) { if let Some(replacement) = self.replacement(&element) { - return match replacement { + match replacement { Replacement::Single(NodeOrToken::Node(it)) => { - Some(NodeOrToken::Node(it.green().clone())) + acc.push(NodeOrToken::Node(it.green().clone())) } Replacement::Single(NodeOrToken::Token(it)) => { - Some(NodeOrToken::Token(it.green().clone())) + acc.push(NodeOrToken::Token(it.green().clone())) } - Replacement::Delete => None, + Replacement::Many(replacements) => { + acc.extend(replacements.iter().map(|it| match it { + NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()), + NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), + })) + } + Replacement::Delete => (), }; + return; } let res = match element { NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()), }; - Some(res) + acc.push(res) } } @@ -341,6 +360,7 @@ fn add_assign(&mut self, rhs: SyntaxRewriter) { enum Replacement { Delete, Single(SyntaxElement), + Many(Vec), } fn with_children( diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 24a1e1d9187..29eb3fcb9cc 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -1,7 +1,10 @@ //! 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 std::{ + fmt, iter, + ops::{self, RangeInclusive}, +}; use arrayvec::ArrayVec; @@ -437,6 +440,28 @@ fn from(level: u8) -> IndentLevel { } } +impl fmt::Display for IndentLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let spaces = " "; + let buf; + let len = self.0 as usize * 4; + let indent = if len <= spaces.len() { + &spaces[..len] + } else { + buf = iter::repeat(' ').take(len).collect::(); + &buf + }; + fmt::Display::fmt(indent, f) + } +} + +impl ops::Add for IndentLevel { + type Output = IndentLevel; + fn add(self, rhs: u8) -> IndentLevel { + IndentLevel(self.0 + rhs) + } +} + impl IndentLevel { pub fn from_node(node: &SyntaxNode) -> IndentLevel { let first_token = match node.first_token() { @@ -453,6 +478,14 @@ pub fn from_node(node: &SyntaxNode) -> IndentLevel { IndentLevel(0) } + /// XXX: this intentionally doesn't change the indent of the very first token. + /// Ie, in something like + /// ``` + /// fn foo() { + /// 92 + /// } + /// ``` + /// if you indent the block, the `{` token would stay put. fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { let mut rewriter = SyntaxRewriter::default(); node.descendants_with_tokens() @@ -463,12 +496,7 @@ fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { text.contains('\n') }) .for_each(|ws| { - let new_ws = make::tokens::whitespace(&format!( - "{}{:width$}", - ws.syntax().text(), - "", - width = self.0 as usize * 4 - )); + let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,)); rewriter.replace(ws.syntax(), &new_ws) }); rewriter.rewrite(&node) @@ -485,7 +513,7 @@ fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode { }) .for_each(|ws| { let new_ws = make::tokens::whitespace( - &ws.syntax().text().replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"), + &ws.syntax().text().replace(&format!("\n{}", self), "\n"), ); rewriter.replace(ws.syntax(), &new_ws) }); diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index d0e960fb497..b275780eca0 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs @@ -277,7 +277,12 @@ pub fn param_list(pats: impl IntoIterator) -> ast::ParamList ast_from_text(&format!("fn f({}) {{ }}", args)) } +pub fn visibility_pub_crate() -> ast::Visibility { + ast_from_text("pub(crate) struct S") +} + pub fn fn_def( + visibility: Option, fn_name: ast::Name, type_params: Option, params: ast::ParamList, @@ -285,21 +290,11 @@ pub fn fn_def( ) -> ast::FnDef { let type_params = if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() }; - ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) -} - -pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { - let newlines = "\n".repeat(amount_of_newlines); - ast_from_text(&format!("{}{}", newlines, t.syntax())) -} - -pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { - let newlines = "\n".repeat(amount_of_newlines); - ast_from_text(&format!("{}{}", t.syntax(), newlines)) -} - -pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef { - ast_from_text(&format!("pub(crate) {}", fn_def)) + let visibility = match visibility { + None => String::new(), + Some(it) => format!("{} ", it), + }; + ast_from_text(&format!("{}fn {}{}{} {}", visibility, fn_name, type_params, params, body)) } fn ast_from_text(text: &str) -> N {