Add AssistConfig

This commit is contained in:
Aleksey Kladov 2020-05-17 12:09:53 +02:00
parent 131849f2ab
commit c847c079fd
13 changed files with 129 additions and 39 deletions

View 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: () }) }
}
}

View file

@ -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
}
}

View file

@ -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);

View file

@ -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");

View file

@ -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)?;

View file

@ -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();

View file

@ -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,

View file

@ -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,

View file

@ -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,
},
},
)

View file

@ -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,
}
}
}

View file

@ -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);

View file

@ -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() {

View file

@ -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())