Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support snippet text edit #4494

Merged
merged 6 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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