From c847c079fd66d7ed09c64ff398baf05317b16500 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 17 May 2020 12:09:53 +0200 Subject: Add AssistConfig --- crates/ra_assists/src/assist_config.rs | 27 +++++++++++++++++ crates/ra_assists/src/assist_context.rs | 51 +++++++++++++++++++++++++++++---- crates/ra_assists/src/lib.rs | 15 +++++++--- crates/ra_assists/src/tests.rs | 38 ++++++++++++------------ 4 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 crates/ra_assists/src/assist_config.rs (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs new file mode 100644 index 000000000..c0a0226fb --- /dev/null +++ b/crates/ra_assists/src/assist_config.rs @@ -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, +} + +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: () }) } + } +} diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index a680f752b..b90bbf8b2 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs @@ -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. /// @@ -48,6 +51,7 @@ 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, @@ -55,10 +59,14 @@ pub(crate) struct AssistContext<'a> { } 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. @@ -165,11 +173,17 @@ pub(crate) struct AssistBuilder { edit: TextEditBuilder, cursor_position: Option, 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. @@ -180,10 +194,30 @@ impl AssistBuilder { pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into) { 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, + ) { + 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) { self.edit.replace(range, replace_with.into()) } + /// Append specified `text` at the given `offset` + pub(crate) fn replace_snippet( + &mut self, + _cap: SnippetCap, + range: TextRange, + replace_with: impl Into, + ) { + self.is_snippet = true; + self.edit.replace(range, replace_with.into()) + } pub(crate) fn replace_ast(&mut self, old: N, new: N) { algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) } @@ -227,7 +261,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 } } diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index b6dc7cb1b..7f0a723c9 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -10,6 +10,7 @@ macro_rules! eprintln { ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; } +mod assist_config; mod assist_context; mod marks; #[cfg(test)] @@ -24,6 +25,8 @@ use ra_syntax::TextRange; pub(crate) use crate::assist_context::{AssistContext, Assists}; +pub use assist_config::AssistConfig; + /// Unique identifier of the assist, should not be shown to the user /// directly. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -54,9 +57,9 @@ impl Assist { /// /// Assists are returned in the "unresolved" state, that is only labels are /// returned, without actual edits. - pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec { + pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec { let sema = Semantics::new(db); - let ctx = AssistContext::new(sema, range); + let ctx = AssistContext::new(sema, config, range); let mut acc = Assists::new_unresolved(&ctx); handlers::all().iter().for_each(|handler| { handler(&mut acc, &ctx); @@ -68,9 +71,13 @@ impl Assist { /// /// Assists are returned in the "resolved" state, that is with edit fully /// computed. - pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec { + pub fn resolved( + db: &RootDatabase, + config: &AssistConfig, + range: FileRange, + ) -> Vec { let sema = Semantics::new(db); - let ctx = AssistContext::new(sema, range); + let ctx = AssistContext::new(sema, config, range); let mut acc = Assists::new_resolved(&ctx); handlers::all().iter().for_each(|handler| { handler(&mut acc, &ctx); diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index a3eacb8f1..9ba3da786 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs @@ -11,7 +11,7 @@ use test_utils::{ RangeOrOffset, }; -use crate::{handlers::Handler, Assist, AssistContext, Assists}; +use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { let (mut db, file_id) = RootDatabase::with_single_file(text); @@ -41,14 +41,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { let (db, file_id) = crate::tests::with_single_file(&before); let frange = FileRange { file_id, range: selection.into() }; - let mut assist = Assist::resolved(&db, frange) + let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) .into_iter() .find(|assist| assist.assist.id.0 == assist_id) .unwrap_or_else(|| { panic!( "\n\nAssist is not applicable: {}\nAvailable assists: {}", assist_id, - Assist::resolved(&db, frange) + Assist::resolved(&db, &AssistConfig::default(), frange) .into_iter() .map(|assist| assist.assist.id.0) .collect::>() @@ -90,7 +90,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; let sema = Semantics::new(&db); - let ctx = AssistContext::new(sema, frange); + let config = AssistConfig::default(); + let ctx = AssistContext::new(sema, &config, frange); let mut acc = Assists::new_resolved(&ctx); handler(&mut acc, &ctx); let mut res = acc.finish_resolved(); @@ -103,19 +104,20 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { let mut actual = db.file_text(change.file_id).as_ref().to_owned(); change.edit.apply(&mut actual); - match source_change.cursor_position { - None => { - if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { - let off = change - .edit - .apply_to_offset(before_cursor_pos) - .expect("cursor position is affected by the edit"); - actual = add_cursor(&actual, off) + if !source_change.is_snippet { + match source_change.cursor_position { + None => { + if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { + let off = change + .edit + .apply_to_offset(before_cursor_pos) + .expect("cursor position is affected by the edit"); + actual = add_cursor(&actual, off) + } } - } - Some(off) => actual = add_cursor(&actual, off.offset), - }; - + Some(off) => actual = add_cursor(&actual, off.offset), + }; + } assert_eq_text!(after, &actual); } (Some(assist), ExpectedResult::Target(target)) => { @@ -136,7 +138,7 @@ fn assist_order_field_struct() { let (before_cursor_pos, before) = extract_offset(before); let (db, file_id) = with_single_file(&before); let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; - let assists = Assist::resolved(&db, frange); + let assists = Assist::resolved(&db, &AssistConfig::default(), frange); let mut assists = assists.iter(); assert_eq!( @@ -159,7 +161,7 @@ fn assist_order_if_expr() { let (range, before) = extract_range(before); let (db, file_id) = with_single_file(&before); let frange = FileRange { file_id, range }; - let assists = Assist::resolved(&db, frange); + let assists = Assist::resolved(&db, &AssistConfig::default(), frange); let mut assists = assists.iter(); assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); -- cgit v1.2.3 From fa2e5299c3332b99fcd09fd54e8d812a6c34b0cc Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 17 May 2020 14:21:24 +0200 Subject: Add snippet support for some assists --- crates/ra_assists/src/handlers/add_custom_impl.rs | 51 ++++++++++++----------- crates/ra_assists/src/handlers/add_derive.rs | 28 ++++++++----- crates/ra_assists/src/handlers/add_impl.rs | 34 ++++++++------- crates/ra_assists/src/tests/generated.rs | 10 ++--- 4 files changed, 67 insertions(+), 56 deletions(-) (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs index 2baeb8607..fa70c8496 100644 --- a/crates/ra_assists/src/handlers/add_custom_impl.rs +++ b/crates/ra_assists/src/handlers/add_custom_impl.rs @@ -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<()> { @@ -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() @@ -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 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() @@ -84,14 +75,24 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option< .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), + ); + } + } }) } @@ -117,7 +118,7 @@ struct Foo { } impl Debug for Foo { -<|> + $0 } ", ) @@ -139,7 +140,7 @@ pub struct Foo { } impl Debug for Foo { -<|> + $0 } ", ) @@ -158,7 +159,7 @@ struct Foo {} struct Foo {} impl Debug for Foo { -<|> + $0 } ", ) diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs index fb08c19e9..b123b8498 100644 --- a/crates/ra_assists/src/handlers/add_derive.rs +++ b/crates/ra_assists/src/handlers/add_derive.rs @@ -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::()?; 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) }) } @@ -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, }", ); } @@ -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, }", ); } @@ -96,7 +102,7 @@ struct Foo { a: i32<|>, } " /// `Foo` is a pretty important struct. /// It does stuff. -#[derive(<|>)] +#[derive($0)] struct Foo { a: i32, } ", ); diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs index df114a0d8..eceba7d0a 100644 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ b/crates/ra_assists/src/handlers/add_impl.rs @@ -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}; @@ -12,17 +9,17 @@ use crate::{AssistContext, AssistId, Assists}; // // ``` // struct Ctx { -// data: T,<|> +// data: T,<|> // } // ``` // -> // ``` // struct Ctx { -// data: T, +// data: T, // } // // impl Ctx { -// +// $0 // } // ``` 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(", "); 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 {<|>}", - "struct Foo {}\n\nimpl Foo {\n<|>\n}", + "struct Foo {}\n\nimpl Foo {\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}", ); } diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 972dbd251..9487c9239 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -15,7 +15,7 @@ struct S; struct S; impl Debug for S { - + $0 } "#####, ) @@ -32,7 +32,7 @@ struct Point { } "#####, r#####" -#[derive()] +#[derive($0)] struct Point { x: u32, y: u32, @@ -108,16 +108,16 @@ fn doctest_add_impl() { "add_impl", r#####" struct Ctx { - data: T,<|> + data: T,<|> } "#####, r#####" struct Ctx { - data: T, + data: T, } impl Ctx { - + $0 } "#####, ) -- cgit v1.2.3 From 2bf6b16a7f174ea3f581f26f642b4febff0b9ce8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 18 May 2020 00:11:40 +0200 Subject: Server side of SnippetTextEdit --- crates/ra_assists/src/assist_context.rs | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index b90bbf8b2..0dcd9df61 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs @@ -208,16 +208,6 @@ impl AssistBuilder { pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into) { self.edit.replace(range, replace_with.into()) } - /// Append specified `text` at the given `offset` - pub(crate) fn replace_snippet( - &mut self, - _cap: SnippetCap, - range: TextRange, - replace_with: impl Into, - ) { - self.is_snippet = true; - self.edit.replace(range, replace_with.into()) - } pub(crate) fn replace_ast(&mut self, old: N, new: N) { algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) } -- cgit v1.2.3