mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Add AssistConfig
This commit is contained in:
parent
131849f2ab
commit
c847c079fd
13 changed files with 129 additions and 39 deletions
27
crates/ra_assists/src/assist_config.rs
Normal file
27
crates/ra_assists/src/assist_config.rs
Normal file
|
@ -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: () }) }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Reference in a new issue