From c847c079fd66d7ed09c64ff398baf05317b16500 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 17 May 2020 12:09:53 +0200 Subject: [PATCH] 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 +++++++------- crates/ra_ide/src/completion.rs | 2 +- crates/ra_ide/src/completion/test_utils.rs | 2 +- crates/ra_ide/src/diagnostics.rs | 2 + crates/ra_ide/src/lib.rs | 10 ++-- crates/ra_ide/src/references/rename.rs | 3 ++ crates/ra_ide_db/src/source_change.rs | 5 ++ .../rust-analyzer/src/cli/analysis_bench.rs | 2 +- crates/rust-analyzer/src/config.rs | 5 +- .../rust-analyzer/src/main_loop/handlers.rs | 6 ++- 13 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 crates/ra_assists/src/assist_config.rs diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs new file mode 100644 index 00000000000..c0a0226fb24 --- /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 a680f752b39..b90bbf8b2e6 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs @@ -15,7 +15,10 @@ }; 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 @@ /// 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 @@ pub(crate) fn delete(&mut self, range: TextRange) { 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 @@ fn finish(self, change_label: String) -> SourceChange { 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 b6dc7cb1bfc..7f0a723c9e7 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 @@ macro_rules! eprintln { 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 @@ pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec { /// /// 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 a3eacb8f115..9ba3da78622 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs @@ -11,7 +11,7 @@ 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 @@ pub fn test_some_range(a: int) -> bool { 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"); diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 8bdc43b1a03..191300704b5 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -59,8 +59,8 @@ /// with ordering of completions (currently this is done by the client). pub(crate) fn completions( db: &RootDatabase, - position: FilePosition, config: &CompletionConfig, + position: FilePosition, ) -> Option { let ctx = CompletionContext::new(db, position, config)?; diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index eb90b5279c4..bf22452a281 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs @@ -20,7 +20,7 @@ pub(crate) fn do_completion_with_options( } else { single_file_with_position(code) }; - let completions = analysis.completions(position, options).unwrap().unwrap(); + let completions = analysis.completions(options, position).unwrap().unwrap(); let completion_items: Vec = completions.into(); let mut kind_completions: Vec = completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 87a0b80f13e..54c2bcc0942 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs @@ -629,6 +629,7 @@ fn test_unresolved_module_diagnostic() { }, ], cursor_position: None, + is_snippet: false, }, ), severity: Error, @@ -685,6 +686,7 @@ pub struct Foo { ], file_system_edits: [], cursor_position: None, + is_snippet: false, }, ), severity: Error, diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 78149ddfcb3..66125f2f59b 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -82,7 +82,7 @@ macro_rules! eprintln { }; pub use hir::Documentation; -pub use ra_assists::AssistId; +pub use ra_assists::{AssistConfig, AssistId}; pub use ra_db::{ Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, }; @@ -458,17 +458,17 @@ pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable Cancelable>> { - self.with_db(|db| completion::completions(db, position, config).map(Into::into)) + self.with_db(|db| completion::completions(db, config, position).map(Into::into)) } /// Computes assists (aka code actions aka intentions) for the given /// position. - pub fn assists(&self, frange: FileRange) -> Cancelable> { + pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable> { self.with_db(|db| { - ra_assists::Assist::resolved(db, frange) + ra_assists::Assist::resolved(db, config, frange) .into_iter() .map(|assist| Assist { id: assist.assist.id, diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 410dae75cb4..68a53ad4b07 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs @@ -670,6 +670,7 @@ fn test_rename_mod() { }, ], cursor_position: None, + is_snippet: false, }, }, ) @@ -722,6 +723,7 @@ fn test_rename_mod_in_dir() { }, ], cursor_position: None, + is_snippet: false, }, }, ) @@ -818,6 +820,7 @@ fn f() { }, ], cursor_position: None, + is_snippet: false, }, }, ) diff --git a/crates/ra_ide_db/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs index af81a91a4a5..c64165f3a90 100644 --- a/crates/ra_ide_db/src/source_change.rs +++ b/crates/ra_ide_db/src/source_change.rs @@ -13,6 +13,7 @@ pub struct SourceChange { pub source_file_edits: Vec, pub file_system_edits: Vec, pub cursor_position: Option, + pub is_snippet: bool, } impl SourceChange { @@ -28,6 +29,7 @@ pub fn from_edits>( source_file_edits, file_system_edits, cursor_position: None, + is_snippet: false, } } @@ -41,6 +43,7 @@ pub fn source_file_edits>(label: L, edits: Vec) source_file_edits: edits, file_system_edits: vec![], cursor_position: None, + is_snippet: false, } } @@ -52,6 +55,7 @@ pub(crate) fn file_system_edits>(label: L, edits: Vec SourceChange { source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], file_system_edits: Vec::new(), cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), + is_snippet: false, } } } diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs index 6147ae20743..b20efe98d8c 100644 --- a/crates/rust-analyzer/src/cli/analysis_bench.rs +++ b/crates/rust-analyzer/src/cli/analysis_bench.rs @@ -105,7 +105,7 @@ pub fn analysis_bench( if is_completion { let options = CompletionConfig::default(); let res = do_work(&mut host, file_id, |analysis| { - analysis.completions(file_position, &options) + analysis.completions(&options, file_position) }); if verbosity.is_verbose() { println!("\n{:#?}", res); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index b5dc6f0fa98..063b1b31617 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -11,7 +11,7 @@ use lsp_types::ClientCapabilities; use ra_flycheck::FlycheckConfig; -use ra_ide::{CompletionConfig, InlayHintsConfig}; +use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig}; use ra_project_model::CargoConfig; use serde::Deserialize; @@ -32,6 +32,7 @@ pub struct Config { pub inlay_hints: InlayHintsConfig, pub completion: CompletionConfig, + pub assist: AssistConfig, pub call_info_full: bool, pub lens: LensConfig, } @@ -136,6 +137,7 @@ fn default() -> Self { add_call_argument_snippets: true, ..CompletionConfig::default() }, + assist: AssistConfig::default(), call_info_full: true, lens: LensConfig::default(), } @@ -281,6 +283,7 @@ pub fn update_caps(&mut self, caps: &ClientCapabilities) { } } } + self.assist.allow_snippets(false); } if let Some(window_caps) = caps.window.as_ref() { diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index e6755675285..13ae061fa0f 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -476,7 +476,7 @@ pub fn handle_completion( return Ok(None); } - let items = match world.analysis().completions(position, &world.config.completion)? { + let items = match world.analysis().completions(&world.config.completion, position)? { None => return Ok(None), Some(items) => items, }; @@ -740,7 +740,9 @@ pub fn handle_code_action( } let mut grouped_assists: FxHashMap)> = FxHashMap::default(); - for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { + for assist in + world.analysis().assists(&world.config.assist, FileRange { file_id, range })?.into_iter() + { match &assist.group_label { Some(label) => grouped_assists .entry(label.to_owned())