about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assist_config.rs27
-rw-r--r--crates/ra_assists/src/assist_context.rs51
-rw-r--r--crates/ra_assists/src/lib.rs15
-rw-r--r--crates/ra_assists/src/tests.rs38
-rw-r--r--crates/ra_ide/src/completion.rs2
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs2
-rw-r--r--crates/ra_ide/src/diagnostics.rs2
-rw-r--r--crates/ra_ide/src/lib.rs10
-rw-r--r--crates/ra_ide/src/references/rename.rs3
-rw-r--r--crates/ra_ide_db/src/source_change.rs5
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs5
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs6
13 files changed, 129 insertions, 39 deletions
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<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: () }) }
+    }
+}
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_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<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.
@@ -180,10 +194,30 @@ 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())
     }
+    /// Append specified `text` at the given `offset`
+    pub(crate) fn replace_snippet(
+        &mut self,
+        _cap: SnippetCap,
+        range: TextRange,
+        replace_with: impl Into<String>,
+    ) {
+        self.is_snippet = true;
+        self.edit.replace(range, replace_with.into())
+    }
     pub(crate) fn replace_ast<N: AstNode>(&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 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 @@ 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<Assist> {
+    pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> {
         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<ResolvedAssist> {
+    pub fn resolved(
+        db: &RootDatabase,
+        config: &AssistConfig,
+        range: FileRange,
+    ) -> Vec<ResolvedAssist> {
         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 @@ 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::<Vec<_>>()
@@ -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");
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 @@ pub use crate::completion::{
 /// with ordering of completions (currently this is done by the client).
 pub(crate) fn completions(
     db: &RootDatabase,
-    position: FilePosition,
     config: &CompletionConfig,
+    position: FilePosition,
 ) -> Option<Completions> {
     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<CompletionItem> = completions.into();
     let mut kind_completions: Vec<CompletionItem> =
         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 @@ mod tests {
                             },
                         ],
                         cursor_position: None,
+                        is_snippet: false,
                     },
                 ),
                 severity: Error,
@@ -685,6 +686,7 @@ mod tests {
                         ],
                         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 @@ pub use crate::{
 };
 
 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 @@ impl Analysis {
     /// Computes completions at the given position.
     pub fn completions(
         &self,
-        position: FilePosition,
         config: &CompletionConfig,
+        position: FilePosition,
     ) -> Cancelable<Option<Vec<CompletionItem>>> {
-        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<Vec<Assist>> {
+    pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> {
         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 @@ mod tests {
                         },
                     ],
                     cursor_position: None,
+                    is_snippet: false,
                 },
             },
         )
@@ -722,6 +723,7 @@ mod tests {
                         },
                     ],
                     cursor_position: None,
+                    is_snippet: false,
                 },
             },
         )
@@ -818,6 +820,7 @@ mod tests {
                         },
                     ],
                     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<SourceFileEdit>,
     pub file_system_edits: Vec<FileSystemEdit>,
     pub cursor_position: Option<FilePosition>,
+    pub is_snippet: bool,
 }
 
 impl SourceChange {
@@ -28,6 +29,7 @@ impl SourceChange {
             source_file_edits,
             file_system_edits,
             cursor_position: None,
+            is_snippet: false,
         }
     }
 
@@ -41,6 +43,7 @@ impl SourceChange {
             source_file_edits: edits,
             file_system_edits: vec![],
             cursor_position: None,
+            is_snippet: false,
         }
     }
 
@@ -52,6 +55,7 @@ impl SourceChange {
             source_file_edits: vec![],
             file_system_edits: edits,
             cursor_position: None,
+            is_snippet: false,
         }
     }
 
@@ -115,6 +119,7 @@ impl SingleFileChange {
             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 std::{ffi::OsString, path::PathBuf};
 
 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 @@ impl Default for Config {
                 add_call_argument_snippets: true,
                 ..CompletionConfig::default()
             },
+            assist: AssistConfig::default(),
             call_info_full: true,
             lens: LensConfig::default(),
         }
@@ -281,6 +283,7 @@ impl Config {
                     }
                 }
             }
+            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<String, (usize, Vec<Assist>)> = 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())