Skip to content

Commit

Permalink
Merge #4494
Browse files Browse the repository at this point in the history
4494: Support snippet text edit r=matklad a=matklad



bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
  • Loading branch information
bors[bot] and matklad authored May 19, 2020
2 parents 131849f + 914fc9b commit 0837008
Show file tree
Hide file tree
Showing 31 changed files with 552 additions and 260 deletions.
27 changes: 27 additions & 0 deletions crates/ra_assists/src/assist_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Settings for tweaking assists.
//!
//! The fun thing here is `SnippetCap` -- this type can only be created in this
//! module, and we use to statically check that we only produce snippet
//! assists if we are allowed to.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AssistConfig {
pub snippet_cap: Option<SnippetCap>,
}

impl AssistConfig {
pub fn allow_snippets(&mut self, yes: bool) {
self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SnippetCap {
_private: (),
}

impl Default for AssistConfig {
fn default() -> Self {
AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) }
}
}
41 changes: 35 additions & 6 deletions crates/ra_assists/src/assist_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use ra_syntax::{
};
use ra_text_edit::TextEditBuilder;

use crate::{Assist, AssistId, GroupLabel, ResolvedAssist};
use crate::{
assist_config::{AssistConfig, SnippetCap},
Assist, AssistId, GroupLabel, ResolvedAssist,
};

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

impl<'a> AssistContext<'a> {
pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> {
pub(crate) fn new(
sema: Semantics<'a, RootDatabase>,
config: &'a AssistConfig,
frange: FileRange,
) -> AssistContext<'a> {
let source_file = sema.parse(frange.file_id);
let db = sema.db;
AssistContext { sema, db, frange, source_file }
AssistContext { config, sema, db, frange, source_file }
}

// NB, this ignores active selection.
Expand Down Expand Up @@ -165,11 +173,17 @@ pub(crate) struct AssistBuilder {
edit: TextEditBuilder,
cursor_position: Option<TextSize>,
file: FileId,
is_snippet: bool,
}

impl AssistBuilder {
pub(crate) fn new(file: FileId) -> AssistBuilder {
AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file }
AssistBuilder {
edit: TextEditBuilder::default(),
cursor_position: None,
file,
is_snippet: false,
}
}

/// Remove specified `range` of text.
Expand All @@ -180,6 +194,16 @@ impl AssistBuilder {
pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
self.edit.insert(offset, text.into())
}
/// Append specified `text` at the given `offset`
pub(crate) fn insert_snippet(
&mut self,
_cap: SnippetCap,
offset: TextSize,
text: impl Into<String>,
) {
self.is_snippet = true;
self.edit.insert(offset, text.into())
}
/// Replaces specified `range` of text with a given string.
pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
self.edit.replace(range, replace_with.into())
Expand Down Expand Up @@ -227,7 +251,12 @@ impl AssistBuilder {
if edit.is_empty() && self.cursor_position.is_none() {
panic!("Only call `add_assist` if the assist can be applied")
}
SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position }
.into_source_change(self.file)
let mut res =
SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position }
.into_source_change(self.file);
if self.is_snippet {
res.is_snippet = true;
}
res
}
}
51 changes: 26 additions & 25 deletions crates/ra_assists/src/handlers/add_custom_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::{
// struct S;
//
// impl Debug for S {
//
// $0
// }
// ```
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
Expand All @@ -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);

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
.syntax()
.descendants_with_tokens()
Expand All @@ -63,35 +63,36 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
let has_more_derives = !new_attr_input.is_empty();
let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();

let mut buf = String::new();
buf.push_str("\n\nimpl ");
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
if has_more_derives {
builder.replace(input.syntax().text_range(), new_attr_input);
} else {
let attr_range = attr.syntax().text_range();
edit.delete(attr_range);
builder.delete(attr_range);

let line_break_range = attr
.syntax()
.next_sibling_or_token()
.filter(|t| t.kind() == WHITESPACE)
.map(|t| t.text_range())
.unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
edit.delete(line_break_range);

attr_range.len() + line_break_range.len()
};

edit.set_cursor(start_offset + TextSize::of(&buf) - cursor_delta);
buf.push_str("\n}");
edit.insert(start_offset, buf);
builder.delete(line_break_range);
}

match ctx.config.snippet_cap {
Some(cap) => {
builder.insert_snippet(
cap,
start_offset,
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),
);
}
}
})
}

Expand All @@ -117,7 +118,7 @@ struct Foo {
}
impl Debug for Foo {
<|>
$0
}
",
)
Expand All @@ -139,7 +140,7 @@ pub struct Foo {
}
impl Debug for Foo {
<|>
$0
}
",
)
Expand All @@ -158,7 +159,7 @@ struct Foo {}
struct Foo {}
impl Debug for Foo {
<|>
$0
}
",
)
Expand Down
28 changes: 17 additions & 11 deletions crates/ra_assists/src/handlers/add_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,37 @@ use crate::{AssistContext, AssistId, Assists};
// ```
// ->
// ```
// #[derive()]
// #[derive($0)]
// struct Point {
// x: u32,
// y: u32,
// }
// ```
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 node_start = derive_insertion_offset(&nominal)?;
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
.attrs()
.filter_map(|x| x.as_simple_call())
.filter(|(name, _arg)| name == "derive")
.map(|(_name, arg)| arg)
.next();
let offset = match derive_attr {
match derive_attr {
None => {
edit.insert(node_start, "#[derive()]\n");
node_start + TextSize::of("#[derive(")
builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
}
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)
})
}

Expand All @@ -66,12 +72,12 @@ mod tests {
check_assist(
add_derive,
"struct Foo { a: i32, <|>}",
"#[derive(<|>)]\nstruct Foo { a: i32, }",
"#[derive($0)]\nstruct Foo { a: i32, }",
);
check_assist(
add_derive,
"struct Foo { <|> a: i32, }",
"#[derive(<|>)]\nstruct Foo { a: i32, }",
"#[derive($0)]\nstruct Foo { a: i32, }",
);
}

Expand All @@ -80,7 +86,7 @@ mod tests {
check_assist(
add_derive,
"#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
"#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
"#[derive(Clone$0)]\nstruct Foo { a: i32, }",
);
}

Expand All @@ -96,7 +102,7 @@ struct Foo { a: i32<|>, }
"
/// `Foo` is a pretty important struct.
/// It does stuff.
#[derive(<|>)]
#[derive($0)]
struct Foo { a: i32, }
",
);
Expand Down
34 changes: 19 additions & 15 deletions crates/ra_assists/src/handlers/add_impl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use ra_syntax::{
ast::{self, AstNode, NameOwner, TypeParamsOwner},
TextSize,
};
use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
use stdx::{format_to, SepBy};

use crate::{AssistContext, AssistId, Assists};
Expand All @@ -12,17 +9,17 @@ use crate::{AssistContext, AssistId, Assists};
//
// ```
// struct Ctx<T: Clone> {
// data: T,<|>
// data: T,<|>
// }
// ```
// ->
// ```
// struct Ctx<T: Clone> {
// data: T,
// data: T,
// }
//
// impl<T: Clone> Ctx<T> {
//
// $0
// }
// ```
pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
Expand Down Expand Up @@ -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(", ");
format_to!(buf, "<{}>", generic_params)
}
buf.push_str(" {\n");
edit.set_cursor(start_offset + TextSize::of(&buf));
buf.push_str("\n}");
edit.insert(start_offset, buf);
match ctx.config.snippet_cap {
Some(cap) => {
buf.push_str(" {\n $0\n}");
edit.insert_snippet(cap, start_offset, buf);
}
None => {
buf.push_str(" {\n}");
edit.insert(start_offset, buf);
}
}
})
}

#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{check_assist, check_assist_target};

use super::*;

#[test]
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(
add_impl,
"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(
add_impl,
"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}",
);
}

Expand Down
Loading

0 comments on commit 0837008

Please sign in to comment.