mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 12:54:58 +00:00
Append new match arms rather than replacing all of them
This means we now retain comments when filling in match arms.
This commit is contained in:
parent
1c2d4135db
commit
ecc2615ba2
2 changed files with 180 additions and 35 deletions
|
@ -7,7 +7,7 @@ use itertools::Itertools;
|
||||||
use ra_ide_db::RootDatabase;
|
use ra_ide_db::RootDatabase;
|
||||||
|
|
||||||
use crate::{Assist, AssistCtx, AssistId};
|
use crate::{Assist, AssistCtx, AssistId};
|
||||||
use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
|
use ra_syntax::ast::{self, make, AstNode, NameOwner};
|
||||||
|
|
||||||
use ast::{MatchArm, Pat};
|
use ast::{MatchArm, Pat};
|
||||||
|
|
||||||
|
@ -97,10 +97,8 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
|
ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
|
||||||
arms.extend(missing_arms);
|
let new_arm_list =
|
||||||
|
match_arm_list.remove_placeholder().append_arms(missing_arms.into_iter());
|
||||||
let indent_level = IndentLevel::from_node(match_arm_list.syntax());
|
|
||||||
let new_arm_list = indent_level.increase_indent(make::match_arm_list(arms));
|
|
||||||
|
|
||||||
edit.target(match_expr.syntax().text_range());
|
edit.target(match_expr.syntax().text_range());
|
||||||
edit.set_cursor(expr.syntax().text_range().start());
|
edit.set_cursor(expr.syntax().text_range().start());
|
||||||
|
@ -655,4 +653,69 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fill_match_arms_preserves_comments() {
|
||||||
|
check_assist(
|
||||||
|
fill_match_arms,
|
||||||
|
r#"
|
||||||
|
enum A {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
}
|
||||||
|
fn foo(a: A) {
|
||||||
|
match a {
|
||||||
|
// TODO: Fill this in<|>
|
||||||
|
A::One => {}
|
||||||
|
// This is where the rest should be
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
enum A {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
}
|
||||||
|
fn foo(a: A) {
|
||||||
|
match <|>a {
|
||||||
|
// TODO: Fill this in
|
||||||
|
A::One => {}
|
||||||
|
// This is where the rest should be
|
||||||
|
A::Two => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fill_match_arms_preserves_comments_empty() {
|
||||||
|
check_assist(
|
||||||
|
fill_match_arms,
|
||||||
|
r#"
|
||||||
|
enum A {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
}
|
||||||
|
fn foo(a: A) {
|
||||||
|
match a {
|
||||||
|
// TODO: Fill this in<|>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
enum A {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
}
|
||||||
|
fn foo(a: A) {
|
||||||
|
match <|>a {
|
||||||
|
// TODO: Fill this in
|
||||||
|
A::One => {}
|
||||||
|
A::Two => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,12 +46,44 @@ impl ast::FnDef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_multiline<N>(node: N) -> N
|
||||||
|
where
|
||||||
|
N: AstNode + Clone,
|
||||||
|
{
|
||||||
|
let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
|
||||||
|
Some(it) => it,
|
||||||
|
None => return node,
|
||||||
|
};
|
||||||
|
let sibling = match l_curly.next_sibling_or_token() {
|
||||||
|
Some(it) => it,
|
||||||
|
None => return node,
|
||||||
|
};
|
||||||
|
let existing_ws = match sibling.as_token() {
|
||||||
|
None => None,
|
||||||
|
Some(tok) if tok.kind() != WHITESPACE => None,
|
||||||
|
Some(ws) => {
|
||||||
|
if ws.text().contains('\n') {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
Some(ws.clone())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let indent = leading_indent(node.syntax()).unwrap_or_default();
|
||||||
|
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
|
||||||
|
let to_insert = iter::once(ws.ws().into());
|
||||||
|
match existing_ws {
|
||||||
|
None => node.insert_children(InsertPosition::After(l_curly), to_insert),
|
||||||
|
Some(ws) => node.replace_children(single_node(ws), to_insert),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ast::ItemList {
|
impl ast::ItemList {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList {
|
pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList {
|
||||||
let mut res = self.clone();
|
let mut res = self.clone();
|
||||||
if !self.syntax().text().contains_char('\n') {
|
if !self.syntax().text().contains_char('\n') {
|
||||||
res = res.make_multiline();
|
res = make_multiline(res);
|
||||||
}
|
}
|
||||||
items.for_each(|it| res = res.append_item(it));
|
items.for_each(|it| res = res.append_item(it));
|
||||||
res
|
res
|
||||||
|
@ -81,35 +113,6 @@ impl ast::ItemList {
|
||||||
fn l_curly(&self) -> Option<SyntaxElement> {
|
fn l_curly(&self) -> Option<SyntaxElement> {
|
||||||
self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
|
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_default();
|
|
||||||
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
|
|
||||||
let to_insert = iter::once(ws.ws().into());
|
|
||||||
match existing_ws {
|
|
||||||
None => self.insert_children(InsertPosition::After(l_curly), to_insert),
|
|
||||||
Some(ws) => self.replace_children(single_node(ws), to_insert),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ast::RecordFieldList {
|
impl ast::RecordFieldList {
|
||||||
|
@ -334,6 +337,85 @@ impl ast::UseTree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ast::MatchArmList {
|
||||||
|
#[must_use]
|
||||||
|
pub fn append_arms(&self, items: impl Iterator<Item = ast::MatchArm>) -> ast::MatchArmList {
|
||||||
|
let mut res = self.clone();
|
||||||
|
res = res.strip_if_only_whitespace();
|
||||||
|
if !res.syntax().text().contains_char('\n') {
|
||||||
|
res = make_multiline(res);
|
||||||
|
}
|
||||||
|
items.for_each(|it| res = res.append_arm(it));
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_if_only_whitespace(&self) -> ast::MatchArmList {
|
||||||
|
let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']);
|
||||||
|
iter.next(); // Eat the curly
|
||||||
|
let mut inner = iter.take_while(|it| it.kind() != T!['}']);
|
||||||
|
if !inner.clone().all(|it| it.kind() == WHITESPACE) {
|
||||||
|
return self.clone();
|
||||||
|
}
|
||||||
|
let start = match inner.next() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return self.clone(),
|
||||||
|
};
|
||||||
|
let end = match inner.last() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => start.clone(),
|
||||||
|
};
|
||||||
|
let res = self.replace_children(start..=end, &mut iter::empty());
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn remove_placeholder(&self) -> ast::MatchArmList {
|
||||||
|
let placeholder = self.arms().find(|arm| {
|
||||||
|
if let Some(ast::Pat::PlaceholderPat(_)) = arm.pat() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
});
|
||||||
|
if let Some(placeholder) = placeholder {
|
||||||
|
let s: SyntaxElement = placeholder.syntax().clone().into();
|
||||||
|
let e = s.clone();
|
||||||
|
self.replace_children(s..=e, &mut iter::empty())
|
||||||
|
} else {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
|
||||||
|
let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => return self.clone(),
|
||||||
|
};
|
||||||
|
let mut sib = r_curly.prev_sibling_or_token();
|
||||||
|
while let Some(s) = sib.clone() {
|
||||||
|
if let Some(tok) = s.as_token() {
|
||||||
|
if tok.kind() != WHITESPACE {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sib = s.prev_sibling_or_token();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let indent = " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default();
|
||||||
|
let sib = match sib {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return self.clone(),
|
||||||
|
};
|
||||||
|
let position = InsertPosition::After(sib.into());
|
||||||
|
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
|
||||||
|
let to_insert: ArrayVec<[SyntaxElement; 2]> =
|
||||||
|
[ws.ws().into(), item.syntax().clone().into()].into();
|
||||||
|
let res = self.insert_children(position, to_insert);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
|
pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
|
||||||
N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
|
N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue