mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 04:53:34 +00:00
Filter assists
This commit is contained in:
parent
853440775d
commit
aa598ecb75
6 changed files with 157 additions and 20 deletions
|
@ -19,7 +19,7 @@ use ra_text_edit::TextEditBuilder;
|
|||
|
||||
use crate::{
|
||||
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.
|
||||
|
@ -57,6 +57,7 @@ pub(crate) struct AssistContext<'a> {
|
|||
pub(crate) sema: Semantics<'a, RootDatabase>,
|
||||
pub(crate) frange: FileRange,
|
||||
source_file: SourceFile,
|
||||
allowed: Option<Vec<AssistKind>>,
|
||||
}
|
||||
|
||||
impl<'a> AssistContext<'a> {
|
||||
|
@ -64,9 +65,10 @@ impl<'a> AssistContext<'a> {
|
|||
sema: Semantics<'a, RootDatabase>,
|
||||
config: &'a AssistConfig,
|
||||
frange: FileRange,
|
||||
allowed: Option<Vec<AssistKind>>,
|
||||
) -> AssistContext<'a> {
|
||||
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 {
|
||||
|
@ -103,14 +105,26 @@ pub(crate) struct Assists {
|
|||
resolve: bool,
|
||||
file: FileId,
|
||||
buf: Vec<(Assist, Option<SourceChange>)>,
|
||||
allowed: Option<Vec<AssistKind>>,
|
||||
}
|
||||
|
||||
impl 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 {
|
||||
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> {
|
||||
|
@ -139,9 +153,13 @@ impl Assists {
|
|||
target: TextRange,
|
||||
f: impl FnOnce(&mut AssistBuilder),
|
||||
) -> Option<()> {
|
||||
if !self.is_allowed(&id) {
|
||||
return None;
|
||||
}
|
||||
let label = Assist::new(id, label.into(), None, target);
|
||||
self.add_impl(label, f)
|
||||
}
|
||||
|
||||
pub(crate) fn add_group(
|
||||
&mut self,
|
||||
group: &GroupLabel,
|
||||
|
@ -150,9 +168,14 @@ impl Assists {
|
|||
target: TextRange,
|
||||
f: impl FnOnce(&mut AssistBuilder),
|
||||
) -> Option<()> {
|
||||
if !self.is_allowed(&id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let label = Assist::new(id, label.into(), Some(group.clone()), target);
|
||||
self.add_impl(label, f)
|
||||
}
|
||||
|
||||
fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
|
||||
let source_change = if self.resolve {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -37,6 +37,25 @@ pub enum AssistKind {
|
|||
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
|
||||
/// directly.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -67,9 +86,14 @@ impl Assist {
|
|||
///
|
||||
/// Assists are returned in the "unresolved" state, that is only labels are
|
||||
/// 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 ctx = AssistContext::new(sema, config, range);
|
||||
let ctx = AssistContext::new(sema, config, range, allowed);
|
||||
let mut acc = Assists::new_unresolved(&ctx);
|
||||
handlers::all().iter().for_each(|handler| {
|
||||
handler(&mut acc, &ctx);
|
||||
|
@ -85,9 +109,10 @@ impl Assist {
|
|||
db: &RootDatabase,
|
||||
config: &AssistConfig,
|
||||
range: FileRange,
|
||||
allowed: Option<Vec<AssistKind>>,
|
||||
) -> Vec<ResolvedAssist> {
|
||||
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);
|
||||
handlers::all().iter().for_each(|handler| {
|
||||
handler(&mut acc, &ctx);
|
||||
|
|
|
@ -6,7 +6,7 @@ use ra_ide_db::RootDatabase;
|
|||
use ra_syntax::TextRange;
|
||||
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;
|
||||
|
||||
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 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()
|
||||
.find(|assist| assist.assist.id.0 == assist_id)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"\n\nAssist is not applicable: {}\nAvailable assists: {}",
|
||||
assist_id,
|
||||
Assist::resolved(&db, &AssistConfig::default(), frange)
|
||||
Assist::resolved(&db, &AssistConfig::default(), frange, None)
|
||||
.into_iter()
|
||||
.map(|assist| assist.assist.id.0)
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -73,7 +73,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) {
|
|||
|
||||
let sema = Semantics::new(&db);
|
||||
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);
|
||||
handler(&mut acc, &ctx);
|
||||
let mut res = acc.finish_resolved();
|
||||
|
@ -105,7 +105,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, &AssistConfig::default(), frange);
|
||||
let assists = Assist::resolved(&db, &AssistConfig::default(), frange, None);
|
||||
let mut assists = assists.iter();
|
||||
|
||||
assert_eq!(
|
||||
|
@ -128,9 +128,49 @@ 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, &AssistConfig::default(), frange);
|
||||
let assists = Assist::resolved(&db, &AssistConfig::default(), frange, None);
|
||||
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");
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -476,8 +476,9 @@ impl Analysis {
|
|||
&self,
|
||||
config: &AssistConfig,
|
||||
frange: FileRange,
|
||||
allowed: Option<Vec<AssistKind>>,
|
||||
) -> 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
|
||||
|
@ -486,8 +487,9 @@ impl Analysis {
|
|||
&self,
|
||||
config: &AssistConfig,
|
||||
frange: FileRange,
|
||||
allowed: Option<Vec<AssistKind>>,
|
||||
) -> 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.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use ra_db::{FileId, FilePosition, FileRange};
|
||||
use ra_ide::{LineCol, LineIndex};
|
||||
use ra_ide::{AssistKind, LineCol, LineIndex};
|
||||
use ra_syntax::{TextRange, TextSize};
|
||||
use vfs::AbsPathBuf;
|
||||
|
||||
|
@ -52,3 +52,17 @@ pub(crate) fn file_range(
|
|||
let range = text_range(&line_index, 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)
|
||||
}
|
||||
|
|
|
@ -746,6 +746,19 @@ fn handle_fixes(
|
|||
let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||
let line_index = snap.analysis.file_line_index(file_id)?;
|
||||
let range = from_proto::text_range(&line_index, params.range);
|
||||
|
||||
match ¶ms.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 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 range = from_proto::text_range(&line_index, params.range);
|
||||
let frange = FileRange { file_id, range };
|
||||
|
||||
let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
|
||||
|
||||
handle_fixes(&snap, ¶ms, &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 {
|
||||
for (index, assist) in
|
||||
snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate()
|
||||
for (index, assist) in snap
|
||||
.analysis
|
||||
.unresolved_assists(&snap.config.assist, frange, only)?
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
res.push(to_proto::unresolved_code_action(&snap, assist, index)?);
|
||||
}
|
||||
} 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)?);
|
||||
}
|
||||
}
|
||||
|
@ -820,8 +841,13 @@ pub(crate) fn handle_resolve_code_action(
|
|||
let line_index = snap.analysis.file_line_index(file_id)?;
|
||||
let range = from_proto::text_range(&line_index, params.code_action_params.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(¶ms.id, ':').unwrap();
|
||||
let index = index.parse::<usize>().unwrap();
|
||||
let assist = &assists[index];
|
||||
|
|
Loading…
Reference in a new issue