Skip to content

Commit 0837008

Browse files
bors[bot]matklad
andauthored
Merge #4494
4494: Support snippet text edit r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 131849f + 914fc9b commit 0837008

31 files changed

+552
-260
lines changed
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//! Settings for tweaking assists.
2+
//!
3+
//! The fun thing here is `SnippetCap` -- this type can only be created in this
4+
//! module, and we use to statically check that we only produce snippet
5+
//! assists if we are allowed to.
6+
7+
#[derive(Clone, Debug, PartialEq, Eq)]
8+
pub struct AssistConfig {
9+
pub snippet_cap: Option<SnippetCap>,
10+
}
11+
12+
impl AssistConfig {
13+
pub fn allow_snippets(&mut self, yes: bool) {
14+
self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
15+
}
16+
}
17+
18+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19+
pub struct SnippetCap {
20+
_private: (),
21+
}
22+
23+
impl Default for AssistConfig {
24+
fn default() -> Self {
25+
AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) }
26+
}
27+
}

crates/ra_assists/src/assist_context.rs

+35-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ use ra_syntax::{
1515
};
1616
use ra_text_edit::TextEditBuilder;
1717

18-
use crate::{Assist, AssistId, GroupLabel, ResolvedAssist};
18+
use crate::{
19+
assist_config::{AssistConfig, SnippetCap},
20+
Assist, AssistId, GroupLabel, ResolvedAssist,
21+
};
1922

2023
/// `AssistContext` allows to apply an assist or check if it could be applied.
2124
///
@@ -48,17 +51,22 @@ use crate::{Assist, AssistId, GroupLabel, ResolvedAssist};
4851
/// moment, because the LSP API is pretty awkward in this place, and it's much
4952
/// easier to just compute the edit eagerly :-)
5053
pub(crate) struct AssistContext<'a> {
54+
pub(crate) config: &'a AssistConfig,
5155
pub(crate) sema: Semantics<'a, RootDatabase>,
5256
pub(crate) db: &'a RootDatabase,
5357
pub(crate) frange: FileRange,
5458
source_file: SourceFile,
5559
}
5660

5761
impl<'a> AssistContext<'a> {
58-
pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> {
62+
pub(crate) fn new(
63+
sema: Semantics<'a, RootDatabase>,
64+
config: &'a AssistConfig,
65+
frange: FileRange,
66+
) -> AssistContext<'a> {
5967
let source_file = sema.parse(frange.file_id);
6068
let db = sema.db;
61-
AssistContext { sema, db, frange, source_file }
69+
AssistContext { config, sema, db, frange, source_file }
6270
}
6371

6472
// NB, this ignores active selection.
@@ -165,11 +173,17 @@ pub(crate) struct AssistBuilder {
165173
edit: TextEditBuilder,
166174
cursor_position: Option<TextSize>,
167175
file: FileId,
176+
is_snippet: bool,
168177
}
169178

170179
impl AssistBuilder {
171180
pub(crate) fn new(file: FileId) -> AssistBuilder {
172-
AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file }
181+
AssistBuilder {
182+
edit: TextEditBuilder::default(),
183+
cursor_position: None,
184+
file,
185+
is_snippet: false,
186+
}
173187
}
174188

175189
/// Remove specified `range` of text.
@@ -180,6 +194,16 @@ impl AssistBuilder {
180194
pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
181195
self.edit.insert(offset, text.into())
182196
}
197+
/// Append specified `text` at the given `offset`
198+
pub(crate) fn insert_snippet(
199+
&mut self,
200+
_cap: SnippetCap,
201+
offset: TextSize,
202+
text: impl Into<String>,
203+
) {
204+
self.is_snippet = true;
205+
self.edit.insert(offset, text.into())
206+
}
183207
/// Replaces specified `range` of text with a given string.
184208
pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
185209
self.edit.replace(range, replace_with.into())
@@ -227,7 +251,12 @@ impl AssistBuilder {
227251
if edit.is_empty() && self.cursor_position.is_none() {
228252
panic!("Only call `add_assist` if the assist can be applied")
229253
}
230-
SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position }
231-
.into_source_change(self.file)
254+
let mut res =
255+
SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position }
256+
.into_source_change(self.file);
257+
if self.is_snippet {
258+
res.is_snippet = true;
259+
}
260+
res
232261
}
233262
}

crates/ra_assists/src/handlers/add_custom_impl.rs

+26-25
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::{
2525
// struct S;
2626
//
2727
// impl Debug for S {
28-
//
28+
// $0
2929
// }
3030
// ```
3131
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<
5252
format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
5353

5454
let target = attr.syntax().text_range();
55-
acc.add(AssistId("add_custom_impl"), label, target, |edit| {
55+
acc.add(AssistId("add_custom_impl"), label, target, |builder| {
5656
let new_attr_input = input
5757
.syntax()
5858
.descendants_with_tokens()
@@ -63,35 +63,36 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
6363
let has_more_derives = !new_attr_input.is_empty();
6464
let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();
6565

66-
let mut buf = String::new();
67-
buf.push_str("\n\nimpl ");
68-
buf.push_str(trait_token.text().as_str());
69-
buf.push_str(" for ");
70-
buf.push_str(annotated_name.as_str());
71-
buf.push_str(" {\n");
72-
73-
let cursor_delta = if has_more_derives {
74-
let delta = input.syntax().text_range().len() - TextSize::of(&new_attr_input);
75-
edit.replace(input.syntax().text_range(), new_attr_input);
76-
delta
66+
if has_more_derives {
67+
builder.replace(input.syntax().text_range(), new_attr_input);
7768
} else {
7869
let attr_range = attr.syntax().text_range();
79-
edit.delete(attr_range);
70+
builder.delete(attr_range);
8071

8172
let line_break_range = attr
8273
.syntax()
8374
.next_sibling_or_token()
8475
.filter(|t| t.kind() == WHITESPACE)
8576
.map(|t| t.text_range())
8677
.unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
87-
edit.delete(line_break_range);
88-
89-
attr_range.len() + line_break_range.len()
90-
};
91-
92-
edit.set_cursor(start_offset + TextSize::of(&buf) - cursor_delta);
93-
buf.push_str("\n}");
94-
edit.insert(start_offset, buf);
78+
builder.delete(line_break_range);
79+
}
80+
81+
match ctx.config.snippet_cap {
82+
Some(cap) => {
83+
builder.insert_snippet(
84+
cap,
85+
start_offset,
86+
format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
87+
);
88+
}
89+
None => {
90+
builder.insert(
91+
start_offset,
92+
format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
93+
);
94+
}
95+
}
9596
})
9697
}
9798

@@ -117,7 +118,7 @@ struct Foo {
117118
}
118119
119120
impl Debug for Foo {
120-
<|>
121+
$0
121122
}
122123
",
123124
)
@@ -139,7 +140,7 @@ pub struct Foo {
139140
}
140141
141142
impl Debug for Foo {
142-
<|>
143+
$0
143144
}
144145
",
145146
)
@@ -158,7 +159,7 @@ struct Foo {}
158159
struct Foo {}
159160
160161
impl Debug for Foo {
161-
<|>
162+
$0
162163
}
163164
",
164165
)

crates/ra_assists/src/handlers/add_derive.rs

+17-11
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,37 @@ use crate::{AssistContext, AssistId, Assists};
1818
// ```
1919
// ->
2020
// ```
21-
// #[derive()]
21+
// #[derive($0)]
2222
// struct Point {
2323
// x: u32,
2424
// y: u32,
2525
// }
2626
// ```
2727
pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28+
let cap = ctx.config.snippet_cap?;
2829
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
2930
let node_start = derive_insertion_offset(&nominal)?;
3031
let target = nominal.syntax().text_range();
31-
acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |edit| {
32+
acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| {
3233
let derive_attr = nominal
3334
.attrs()
3435
.filter_map(|x| x.as_simple_call())
3536
.filter(|(name, _arg)| name == "derive")
3637
.map(|(_name, arg)| arg)
3738
.next();
38-
let offset = match derive_attr {
39+
match derive_attr {
3940
None => {
40-
edit.insert(node_start, "#[derive()]\n");
41-
node_start + TextSize::of("#[derive(")
41+
builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
42+
}
43+
Some(tt) => {
44+
// Just move the cursor.
45+
builder.insert_snippet(
46+
cap,
47+
tt.syntax().text_range().end() - TextSize::of(')'),
48+
"$0",
49+
)
4250
}
43-
Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
4451
};
45-
edit.set_cursor(offset)
4652
})
4753
}
4854

@@ -66,12 +72,12 @@ mod tests {
6672
check_assist(
6773
add_derive,
6874
"struct Foo { a: i32, <|>}",
69-
"#[derive(<|>)]\nstruct Foo { a: i32, }",
75+
"#[derive($0)]\nstruct Foo { a: i32, }",
7076
);
7177
check_assist(
7278
add_derive,
7379
"struct Foo { <|> a: i32, }",
74-
"#[derive(<|>)]\nstruct Foo { a: i32, }",
80+
"#[derive($0)]\nstruct Foo { a: i32, }",
7581
);
7682
}
7783

@@ -80,7 +86,7 @@ mod tests {
8086
check_assist(
8187
add_derive,
8288
"#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
83-
"#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
89+
"#[derive(Clone$0)]\nstruct Foo { a: i32, }",
8490
);
8591
}
8692

@@ -96,7 +102,7 @@ struct Foo { a: i32<|>, }
96102
"
97103
/// `Foo` is a pretty important struct.
98104
/// It does stuff.
99-
#[derive(<|>)]
105+
#[derive($0)]
100106
struct Foo { a: i32, }
101107
",
102108
);

crates/ra_assists/src/handlers/add_impl.rs

+19-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use ra_syntax::{
2-
ast::{self, AstNode, NameOwner, TypeParamsOwner},
3-
TextSize,
4-
};
1+
use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
52
use stdx::{format_to, SepBy};
63

74
use crate::{AssistContext, AssistId, Assists};
@@ -12,17 +9,17 @@ use crate::{AssistContext, AssistId, Assists};
129
//
1310
// ```
1411
// struct Ctx<T: Clone> {
15-
// data: T,<|>
12+
// data: T,<|>
1613
// }
1714
// ```
1815
// ->
1916
// ```
2017
// struct Ctx<T: Clone> {
21-
// data: T,
18+
// data: T,
2219
// }
2320
//
2421
// impl<T: Clone> Ctx<T> {
25-
//
22+
// $0
2623
// }
2724
// ```
2825
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<()> {
5047
let generic_params = lifetime_params.chain(type_params).sep_by(", ");
5148
format_to!(buf, "<{}>", generic_params)
5249
}
53-
buf.push_str(" {\n");
54-
edit.set_cursor(start_offset + TextSize::of(&buf));
55-
buf.push_str("\n}");
56-
edit.insert(start_offset, buf);
50+
match ctx.config.snippet_cap {
51+
Some(cap) => {
52+
buf.push_str(" {\n $0\n}");
53+
edit.insert_snippet(cap, start_offset, buf);
54+
}
55+
None => {
56+
buf.push_str(" {\n}");
57+
edit.insert(start_offset, buf);
58+
}
59+
}
5760
})
5861
}
5962

6063
#[cfg(test)]
6164
mod tests {
62-
use super::*;
6365
use crate::tests::{check_assist, check_assist_target};
6466

67+
use super::*;
68+
6569
#[test]
6670
fn test_add_impl() {
67-
check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n");
71+
check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n");
6872
check_assist(
6973
add_impl,
7074
"struct Foo<T: Clone> {<|>}",
71-
"struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
75+
"struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
7276
);
7377
check_assist(
7478
add_impl,
7579
"struct Foo<'a, T: Foo<'a>> {<|>}",
76-
"struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}",
80+
"struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
7781
);
7882
}
7983

0 commit comments

Comments
 (0)