Filter assists

This commit is contained in:
kjeremy 2020-07-13 17:41:47 -04:00
parent 853440775d
commit aa598ecb75
6 changed files with 157 additions and 20 deletions

View file

@ -19,7 +19,7 @@ use ra_text_edit::TextEditBuilder;
use crate::{ use crate::{
assist_config::{AssistConfig, SnippetCap}, assist_config::{AssistConfig, SnippetCap},
Assist, AssistId, GroupLabel, ResolvedAssist, Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
}; };
/// `AssistContext` allows to apply an assist or check if it could be applied. /// `AssistContext` allows to apply an assist or check if it could be applied.
@ -57,6 +57,7 @@ pub(crate) struct AssistContext<'a> {
pub(crate) sema: Semantics<'a, RootDatabase>, pub(crate) sema: Semantics<'a, RootDatabase>,
pub(crate) frange: FileRange, pub(crate) frange: FileRange,
source_file: SourceFile, source_file: SourceFile,
allowed: Option<Vec<AssistKind>>,
} }
impl<'a> AssistContext<'a> { impl<'a> AssistContext<'a> {
@ -64,9 +65,10 @@ impl<'a> AssistContext<'a> {
sema: Semantics<'a, RootDatabase>, sema: Semantics<'a, RootDatabase>,
config: &'a AssistConfig, config: &'a AssistConfig,
frange: FileRange, frange: FileRange,
allowed: Option<Vec<AssistKind>>,
) -> AssistContext<'a> { ) -> AssistContext<'a> {
let source_file = sema.parse(frange.file_id); let source_file = sema.parse(frange.file_id);
AssistContext { config, sema, frange, source_file } AssistContext { config, sema, frange, source_file, allowed }
} }
pub(crate) fn db(&self) -> &RootDatabase { pub(crate) fn db(&self) -> &RootDatabase {
@ -103,14 +105,26 @@ pub(crate) struct Assists {
resolve: bool, resolve: bool,
file: FileId, file: FileId,
buf: Vec<(Assist, Option<SourceChange>)>, buf: Vec<(Assist, Option<SourceChange>)>,
allowed: Option<Vec<AssistKind>>,
} }
impl Assists { impl Assists {
pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } Assists {
resolve: true,
file: ctx.frange.file_id,
buf: Vec::new(),
allowed: ctx.allowed.clone(),
}
} }
pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } Assists {
resolve: false,
file: ctx.frange.file_id,
buf: Vec::new(),
allowed: ctx.allowed.clone(),
}
} }
pub(crate) fn finish_unresolved(self) -> Vec<Assist> { pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
@ -139,9 +153,13 @@ impl Assists {
target: TextRange, target: TextRange,
f: impl FnOnce(&mut AssistBuilder), f: impl FnOnce(&mut AssistBuilder),
) -> Option<()> { ) -> Option<()> {
if !self.is_allowed(&id) {
return None;
}
let label = Assist::new(id, label.into(), None, target); let label = Assist::new(id, label.into(), None, target);
self.add_impl(label, f) self.add_impl(label, f)
} }
pub(crate) fn add_group( pub(crate) fn add_group(
&mut self, &mut self,
group: &GroupLabel, group: &GroupLabel,
@ -150,9 +168,14 @@ impl Assists {
target: TextRange, target: TextRange,
f: impl FnOnce(&mut AssistBuilder), f: impl FnOnce(&mut AssistBuilder),
) -> Option<()> { ) -> Option<()> {
if !self.is_allowed(&id) {
return None;
}
let label = Assist::new(id, label.into(), Some(group.clone()), target); let label = Assist::new(id, label.into(), Some(group.clone()), target);
self.add_impl(label, f) self.add_impl(label, f)
} }
fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
let source_change = if self.resolve { let source_change = if self.resolve {
let mut builder = AssistBuilder::new(self.file); let mut builder = AssistBuilder::new(self.file);
@ -170,6 +193,13 @@ impl Assists {
self.buf.sort_by_key(|(label, _edit)| label.target.len()); self.buf.sort_by_key(|(label, _edit)| label.target.len());
self.buf self.buf
} }
fn is_allowed(&self, id: &AssistId) -> bool {
match &self.allowed {
Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
None => true,
}
}
} }
pub(crate) struct AssistBuilder { pub(crate) struct AssistBuilder {

View file

@ -37,6 +37,25 @@ pub enum AssistKind {
RefactorRewrite, RefactorRewrite,
} }
impl AssistKind {
pub fn contains(self, other: AssistKind) -> bool {
if self == other {
return true;
}
match self {
AssistKind::None | AssistKind::Generate => return true,
AssistKind::Refactor => match other {
AssistKind::RefactorExtract
| AssistKind::RefactorInline
| AssistKind::RefactorRewrite => return true,
_ => return false,
},
_ => return false,
}
}
}
/// Unique identifier of the assist, should not be shown to the user /// Unique identifier of the assist, should not be shown to the user
/// directly. /// directly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -67,9 +86,14 @@ impl Assist {
/// ///
/// Assists are returned in the "unresolved" state, that is only labels are /// Assists are returned in the "unresolved" state, that is only labels are
/// returned, without actual edits. /// returned, without actual edits.
pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> { pub fn unresolved(
db: &RootDatabase,
config: &AssistConfig,
range: FileRange,
allowed: Option<Vec<AssistKind>>,
) -> Vec<Assist> {
let sema = Semantics::new(db); let sema = Semantics::new(db);
let ctx = AssistContext::new(sema, config, range); let ctx = AssistContext::new(sema, config, range, allowed);
let mut acc = Assists::new_unresolved(&ctx); let mut acc = Assists::new_unresolved(&ctx);
handlers::all().iter().for_each(|handler| { handlers::all().iter().for_each(|handler| {
handler(&mut acc, &ctx); handler(&mut acc, &ctx);
@ -85,9 +109,10 @@ impl Assist {
db: &RootDatabase, db: &RootDatabase,
config: &AssistConfig, config: &AssistConfig,
range: FileRange, range: FileRange,
allowed: Option<Vec<AssistKind>>,
) -> Vec<ResolvedAssist> { ) -> Vec<ResolvedAssist> {
let sema = Semantics::new(db); let sema = Semantics::new(db);
let ctx = AssistContext::new(sema, config, range); let ctx = AssistContext::new(sema, config, range, allowed);
let mut acc = Assists::new_resolved(&ctx); let mut acc = Assists::new_resolved(&ctx);
handlers::all().iter().for_each(|handler| { handlers::all().iter().for_each(|handler| {
handler(&mut acc, &ctx); handler(&mut acc, &ctx);

View file

@ -6,7 +6,7 @@ use ra_ide_db::RootDatabase;
use ra_syntax::TextRange; use ra_syntax::TextRange;
use test_utils::{assert_eq_text, extract_offset, extract_range}; use test_utils::{assert_eq_text, extract_offset, extract_range};
use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
use stdx::trim_indent; use stdx::trim_indent;
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
@ -35,14 +35,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
let before = db.file_text(file_id).to_string(); let before = db.file_text(file_id).to_string();
let frange = FileRange { file_id, range: selection.into() }; let frange = FileRange { file_id, range: selection.into() };
let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange, None)
.into_iter() .into_iter()
.find(|assist| assist.assist.id.0 == assist_id) .find(|assist| assist.assist.id.0 == assist_id)
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"\n\nAssist is not applicable: {}\nAvailable assists: {}", "\n\nAssist is not applicable: {}\nAvailable assists: {}",
assist_id, assist_id,
Assist::resolved(&db, &AssistConfig::default(), frange) Assist::resolved(&db, &AssistConfig::default(), frange, None)
.into_iter() .into_iter()
.map(|assist| assist.assist.id.0) .map(|assist| assist.assist.id.0)
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -73,7 +73,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) {
let sema = Semantics::new(&db); let sema = Semantics::new(&db);
let config = AssistConfig::default(); let config = AssistConfig::default();
let ctx = AssistContext::new(sema, &config, frange); let ctx = AssistContext::new(sema, &config, frange, None);
let mut acc = Assists::new_resolved(&ctx); let mut acc = Assists::new_resolved(&ctx);
handler(&mut acc, &ctx); handler(&mut acc, &ctx);
let mut res = acc.finish_resolved(); let mut res = acc.finish_resolved();
@ -105,7 +105,7 @@ fn assist_order_field_struct() {
let (before_cursor_pos, before) = extract_offset(before); let (before_cursor_pos, before) = extract_offset(before);
let (db, file_id) = with_single_file(&before); let (db, file_id) = with_single_file(&before);
let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
let assists = Assist::resolved(&db, &AssistConfig::default(), frange); let assists = Assist::resolved(&db, &AssistConfig::default(), frange, None);
let mut assists = assists.iter(); let mut assists = assists.iter();
assert_eq!( assert_eq!(
@ -128,9 +128,49 @@ fn assist_order_if_expr() {
let (range, before) = extract_range(before); let (range, before) = extract_range(before);
let (db, file_id) = with_single_file(&before); let (db, file_id) = with_single_file(&before);
let frange = FileRange { file_id, range }; let frange = FileRange { file_id, range };
let assists = Assist::resolved(&db, &AssistConfig::default(), frange); let assists = Assist::resolved(&db, &AssistConfig::default(), frange, None);
let mut assists = assists.iter(); let mut assists = assists.iter();
assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
} }
#[test]
fn assist_filter_works() {
let before = "
pub fn test_some_range(a: int) -> bool {
if let 2..6 = <|>5<|> {
true
} else {
false
}
}";
let (range, before) = extract_range(before);
let (db, file_id) = with_single_file(&before);
let frange = FileRange { file_id, range };
{
let allowed = Some(vec![AssistKind::Refactor]);
let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed);
let mut assists = assists.iter();
assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
}
{
let allowed = Some(vec![AssistKind::RefactorExtract]);
let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed);
assert_eq!(assists.len(), 1);
let mut assists = assists.iter();
assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
}
{
let allowed = Some(vec![AssistKind::QuickFix]);
let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed);
assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out");
}
}

View file

@ -476,8 +476,9 @@ impl Analysis {
&self, &self,
config: &AssistConfig, config: &AssistConfig,
frange: FileRange, frange: FileRange,
allowed: Option<Vec<AssistKind>>,
) -> Cancelable<Vec<ResolvedAssist>> { ) -> Cancelable<Vec<ResolvedAssist>> {
self.with_db(|db| ra_assists::Assist::resolved(db, config, frange)) self.with_db(|db| ra_assists::Assist::resolved(db, config, frange, allowed))
} }
/// Computes unresolved assists (aka code actions aka intentions) for the given /// Computes unresolved assists (aka code actions aka intentions) for the given
@ -486,8 +487,9 @@ impl Analysis {
&self, &self,
config: &AssistConfig, config: &AssistConfig,
frange: FileRange, frange: FileRange,
allowed: Option<Vec<AssistKind>>,
) -> Cancelable<Vec<Assist>> { ) -> Cancelable<Vec<Assist>> {
self.with_db(|db| Assist::unresolved(db, config, frange)) self.with_db(|db| Assist::unresolved(db, config, frange, allowed))
} }
/// Computes the set of diagnostics for the given file. /// Computes the set of diagnostics for the given file.

View file

@ -2,7 +2,7 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use ra_db::{FileId, FilePosition, FileRange}; use ra_db::{FileId, FilePosition, FileRange};
use ra_ide::{LineCol, LineIndex}; use ra_ide::{AssistKind, LineCol, LineIndex};
use ra_syntax::{TextRange, TextSize}; use ra_syntax::{TextRange, TextSize};
use vfs::AbsPathBuf; use vfs::AbsPathBuf;
@ -52,3 +52,17 @@ pub(crate) fn file_range(
let range = text_range(&line_index, range); let range = text_range(&line_index, range);
Ok(FileRange { file_id, range }) Ok(FileRange { file_id, range })
} }
pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> {
let assist_kind = match &kind {
k if k == &lsp_types::CodeActionKind::EMPTY => AssistKind::None,
k if k == &lsp_types::CodeActionKind::QUICKFIX => AssistKind::QuickFix,
k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::Refactor,
k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorExtract,
k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorInline,
k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorRewrite,
_ => return None,
};
Some(assist_kind)
}

View file

@ -746,6 +746,19 @@ fn handle_fixes(
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.analysis.file_line_index(file_id)?; let line_index = snap.analysis.file_line_index(file_id)?;
let range = from_proto::text_range(&line_index, params.range); let range = from_proto::text_range(&line_index, params.range);
match &params.context.only {
Some(v) => {
if v.iter().any(|it| {
it == &lsp_types::CodeActionKind::EMPTY
|| it == &lsp_types::CodeActionKind::QUICKFIX
}) {
return Ok(());
}
}
None => {}
};
let diagnostics = snap.analysis.diagnostics(file_id)?; let diagnostics = snap.analysis.diagnostics(file_id)?;
let fixes_from_diagnostics = diagnostics let fixes_from_diagnostics = diagnostics
@ -792,18 +805,26 @@ pub(crate) fn handle_code_action(
let line_index = snap.analysis.file_line_index(file_id)?; let line_index = snap.analysis.file_line_index(file_id)?;
let range = from_proto::text_range(&line_index, params.range); let range = from_proto::text_range(&line_index, params.range);
let frange = FileRange { file_id, range }; let frange = FileRange { file_id, range };
let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
handle_fixes(&snap, &params, &mut res)?; handle_fixes(&snap, &params, &mut res)?;
let only =
params.context.only.map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
if snap.config.client_caps.resolve_code_action { if snap.config.client_caps.resolve_code_action {
for (index, assist) in for (index, assist) in snap
snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate() .analysis
.unresolved_assists(&snap.config.assist, frange, only)?
.into_iter()
.enumerate()
{ {
res.push(to_proto::unresolved_code_action(&snap, assist, index)?); res.push(to_proto::unresolved_code_action(&snap, assist, index)?);
} }
} else { } else {
for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() { for assist in snap.analysis.resolved_assists(&snap.config.assist, frange, only)?.into_iter()
{
res.push(to_proto::resolved_code_action(&snap, assist)?); res.push(to_proto::resolved_code_action(&snap, assist)?);
} }
} }
@ -820,8 +841,13 @@ pub(crate) fn handle_resolve_code_action(
let line_index = snap.analysis.file_line_index(file_id)?; let line_index = snap.analysis.file_line_index(file_id)?;
let range = from_proto::text_range(&line_index, params.code_action_params.range); let range = from_proto::text_range(&line_index, params.code_action_params.range);
let frange = FileRange { file_id, range }; let frange = FileRange { file_id, range };
let only = params
.code_action_params
.context
.only
.map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
let assists = snap.analysis.resolved_assists(&snap.config.assist, frange)?; let assists = snap.analysis.resolved_assists(&snap.config.assist, frange, only)?;
let (id_string, index) = split_delim(&params.id, ':').unwrap(); let (id_string, index) = split_delim(&params.id, ':').unwrap();
let index = index.parse::<usize>().unwrap(); let index = index.parse::<usize>().unwrap();
let assist = &assists[index]; let assist = &assists[index];