Add snippet support for some assists

This commit is contained in:
Aleksey Kladov 2020-05-17 14:21:24 +02:00
parent c847c079fd
commit fa2e5299c3
5 changed files with 71 additions and 60 deletions

View file

@ -25,7 +25,7 @@ use crate::{
// struct S; // struct S;
// //
// impl Debug for S { // impl Debug for S {
// // $0
// } // }
// ``` // ```
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
let target = attr.syntax().text_range(); let target = attr.syntax().text_range();
acc.add(AssistId("add_custom_impl"), label, target, |edit| { acc.add(AssistId("add_custom_impl"), label, target, |builder| {
let new_attr_input = input let new_attr_input = input
.syntax() .syntax()
.descendants_with_tokens() .descendants_with_tokens()
@ -63,20 +63,11 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
let has_more_derives = !new_attr_input.is_empty(); let has_more_derives = !new_attr_input.is_empty();
let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string(); let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();
let mut buf = String::new(); if has_more_derives {
buf.push_str("\n\nimpl "); builder.replace(input.syntax().text_range(), new_attr_input);
buf.push_str(trait_token.text().as_str());
buf.push_str(" for ");
buf.push_str(annotated_name.as_str());
buf.push_str(" {\n");
let cursor_delta = if has_more_derives {
let delta = input.syntax().text_range().len() - TextSize::of(&new_attr_input);
edit.replace(input.syntax().text_range(), new_attr_input);
delta
} else { } else {
let attr_range = attr.syntax().text_range(); let attr_range = attr.syntax().text_range();
edit.delete(attr_range); builder.delete(attr_range);
let line_break_range = attr let line_break_range = attr
.syntax() .syntax()
@ -84,14 +75,24 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
.filter(|t| t.kind() == WHITESPACE) .filter(|t| t.kind() == WHITESPACE)
.map(|t| t.text_range()) .map(|t| t.text_range())
.unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
edit.delete(line_break_range); builder.delete(line_break_range);
}
attr_range.len() + line_break_range.len() match ctx.config.snippet_cap {
}; Some(cap) => {
builder.insert_snippet(
edit.set_cursor(start_offset + TextSize::of(&buf) - cursor_delta); cap,
buf.push_str("\n}"); start_offset,
edit.insert(start_offset, buf); format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
);
}
None => {
builder.insert(
start_offset,
format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
);
}
}
}) })
} }
@ -117,7 +118,7 @@ struct Foo {
} }
impl Debug for Foo { impl Debug for Foo {
<|> $0
} }
", ",
) )
@ -139,7 +140,7 @@ pub struct Foo {
} }
impl Debug for Foo { impl Debug for Foo {
<|> $0
} }
", ",
) )
@ -158,7 +159,7 @@ struct Foo {}
struct Foo {} struct Foo {}
impl Debug for Foo { impl Debug for Foo {
<|> $0
} }
", ",
) )

View file

@ -18,31 +18,37 @@ use crate::{AssistContext, AssistId, Assists};
// ``` // ```
// -> // ->
// ``` // ```
// #[derive()] // #[derive($0)]
// struct Point { // struct Point {
// x: u32, // x: u32,
// y: u32, // y: u32,
// } // }
// ``` // ```
pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let cap = ctx.config.snippet_cap?;
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
let node_start = derive_insertion_offset(&nominal)?; let node_start = derive_insertion_offset(&nominal)?;
let target = nominal.syntax().text_range(); let target = nominal.syntax().text_range();
acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| {
let derive_attr = nominal let derive_attr = nominal
.attrs() .attrs()
.filter_map(|x| x.as_simple_call()) .filter_map(|x| x.as_simple_call())
.filter(|(name, _arg)| name == "derive") .filter(|(name, _arg)| name == "derive")
.map(|(_name, arg)| arg) .map(|(_name, arg)| arg)
.next(); .next();
let offset = match derive_attr { match derive_attr {
None => { None => {
edit.insert(node_start, "#[derive()]\n"); builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
node_start + TextSize::of("#[derive(") }
Some(tt) => {
// Just move the cursor.
builder.insert_snippet(
cap,
tt.syntax().text_range().end() - TextSize::of(')'),
"$0",
)
} }
Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
}; };
edit.set_cursor(offset)
}) })
} }
@ -66,12 +72,12 @@ mod tests {
check_assist( check_assist(
add_derive, add_derive,
"struct Foo { a: i32, <|>}", "struct Foo { a: i32, <|>}",
"#[derive(<|>)]\nstruct Foo { a: i32, }", "#[derive($0)]\nstruct Foo { a: i32, }",
); );
check_assist( check_assist(
add_derive, add_derive,
"struct Foo { <|> a: i32, }", "struct Foo { <|> a: i32, }",
"#[derive(<|>)]\nstruct Foo { a: i32, }", "#[derive($0)]\nstruct Foo { a: i32, }",
); );
} }
@ -80,7 +86,7 @@ mod tests {
check_assist( check_assist(
add_derive, add_derive,
"#[derive(Clone)]\nstruct Foo { a: i32<|>, }", "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
"#[derive(Clone<|>)]\nstruct Foo { a: i32, }", "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
); );
} }
@ -96,7 +102,7 @@ struct Foo { a: i32<|>, }
" "
/// `Foo` is a pretty important struct. /// `Foo` is a pretty important struct.
/// It does stuff. /// It does stuff.
#[derive(<|>)] #[derive($0)]
struct Foo { a: i32, } struct Foo { a: i32, }
", ",
); );

View file

@ -1,7 +1,4 @@
use ra_syntax::{ use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
ast::{self, AstNode, NameOwner, TypeParamsOwner},
TextSize,
};
use stdx::{format_to, SepBy}; use stdx::{format_to, SepBy};
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, Assists};
@ -22,7 +19,7 @@ use crate::{AssistContext, AssistId, Assists};
// } // }
// //
// impl<T: Clone> Ctx<T> { // impl<T: Clone> Ctx<T> {
// // $0
// } // }
// ``` // ```
pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@ -50,30 +47,37 @@ pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let generic_params = lifetime_params.chain(type_params).sep_by(", "); let generic_params = lifetime_params.chain(type_params).sep_by(", ");
format_to!(buf, "<{}>", generic_params) format_to!(buf, "<{}>", generic_params)
} }
buf.push_str(" {\n"); match ctx.config.snippet_cap {
edit.set_cursor(start_offset + TextSize::of(&buf)); Some(cap) => {
buf.push_str("\n}"); buf.push_str(" {\n $0\n}");
edit.insert_snippet(cap, start_offset, buf);
}
None => {
buf.push_str(" {\n}");
edit.insert(start_offset, buf); edit.insert(start_offset, buf);
}
}
}) })
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use crate::tests::{check_assist, check_assist_target}; use crate::tests::{check_assist, check_assist_target};
use super::*;
#[test] #[test]
fn test_add_impl() { fn test_add_impl() {
check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n");
check_assist( check_assist(
add_impl, add_impl,
"struct Foo<T: Clone> {<|>}", "struct Foo<T: Clone> {<|>}",
"struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
); );
check_assist( check_assist(
add_impl, add_impl,
"struct Foo<'a, T: Foo<'a>> {<|>}", "struct Foo<'a, T: Foo<'a>> {<|>}",
"struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
); );
} }

View file

@ -15,7 +15,7 @@ struct S;
struct S; struct S;
impl Debug for S { impl Debug for S {
$0
} }
"#####, "#####,
) )
@ -32,7 +32,7 @@ struct Point {
} }
"#####, "#####,
r#####" r#####"
#[derive()] #[derive($0)]
struct Point { struct Point {
x: u32, x: u32,
y: u32, y: u32,
@ -117,7 +117,7 @@ struct Ctx<T: Clone> {
} }
impl<T: Clone> Ctx<T> { impl<T: Clone> Ctx<T> {
$0
} }
"#####, "#####,
) )

View file

@ -17,7 +17,7 @@ struct S;
struct S; struct S;
impl Debug for S { impl Debug for S {
$0
} }
``` ```
@ -33,7 +33,7 @@ struct Point {
} }
// AFTER // AFTER
#[derive()] #[derive($0)]
struct Point { struct Point {
x: u32, x: u32,
y: u32, y: u32,
@ -114,7 +114,7 @@ struct Ctx<T: Clone> {
} }
impl<T: Clone> Ctx<T> { impl<T: Clone> Ctx<T> {
$0
} }
``` ```