mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-27 20:35:09 +00:00
Merge #5350
5350: Filter assists r=matklad a=kjeremy Uses the `CodeActionContext::only` field to compute only those assists the client cares about. It works but I don't really like the implementation. Co-authored-by: kjeremy <kjeremy@gmail.com> Co-authored-by: Jeremy Kolb <kjeremy@gmail.com>
This commit is contained in:
commit
b63e23e98e
6 changed files with 141 additions and 8 deletions
|
@ -4,9 +4,12 @@
|
||||||
//! module, and we use to statically check that we only produce snippet
|
//! module, and we use to statically check that we only produce snippet
|
||||||
//! assists if we are allowed to.
|
//! assists if we are allowed to.
|
||||||
|
|
||||||
|
use crate::AssistKind;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct AssistConfig {
|
pub struct AssistConfig {
|
||||||
pub snippet_cap: Option<SnippetCap>,
|
pub snippet_cap: Option<SnippetCap>,
|
||||||
|
pub allowed: Option<Vec<AssistKind>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistConfig {
|
impl AssistConfig {
|
||||||
|
@ -22,6 +25,6 @@ pub struct SnippetCap {
|
||||||
|
|
||||||
impl Default for AssistConfig {
|
impl Default for AssistConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) }
|
AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -103,14 +103,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.config.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.config.allowed.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
|
pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
|
||||||
|
@ -139,9 +151,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 +166,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 +191,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 {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -134,3 +134,46 @@ fn assist_order_if_expr() {
|
||||||
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 mut cfg = AssistConfig::default();
|
||||||
|
cfg.allowed = Some(vec![AssistKind::Refactor]);
|
||||||
|
|
||||||
|
let assists = Assist::resolved(&db, &cfg, frange);
|
||||||
|
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 mut cfg = AssistConfig::default();
|
||||||
|
cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
|
||||||
|
let assists = Assist::resolved(&db, &cfg, frange);
|
||||||
|
assert_eq!(assists.len(), 1);
|
||||||
|
|
||||||
|
let mut assists = assists.iter();
|
||||||
|
assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cfg = AssistConfig::default();
|
||||||
|
cfg.allowed = Some(vec![AssistKind::QuickFix]);
|
||||||
|
let assists = Assist::resolved(&db, &cfg, frange);
|
||||||
|
assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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_EXTRACT => AssistKind::RefactorExtract,
|
||||||
|
k if k == &lsp_types::CodeActionKind::REFACTOR_INLINE => AssistKind::RefactorInline,
|
||||||
|
k if k == &lsp_types::CodeActionKind::REFACTOR_REWRITE => 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 file_id = from_proto::file_id(&snap, ¶ms.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 ¶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 diagnostics = snap.analysis.diagnostics(file_id)?;
|
||||||
|
|
||||||
let fixes_from_diagnostics = diagnostics
|
let fixes_from_diagnostics = diagnostics
|
||||||
|
@ -777,7 +790,7 @@ fn handle_fixes(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_code_action(
|
pub(crate) fn handle_code_action(
|
||||||
snap: GlobalStateSnapshot,
|
mut snap: GlobalStateSnapshot,
|
||||||
params: lsp_types::CodeActionParams,
|
params: lsp_types::CodeActionParams,
|
||||||
) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
|
) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
|
||||||
let _p = profile("handle_code_action");
|
let _p = profile("handle_code_action");
|
||||||
|
@ -792,6 +805,13 @@ 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 };
|
||||||
|
|
||||||
|
snap.config.assist.allowed = params
|
||||||
|
.clone()
|
||||||
|
.context
|
||||||
|
.only
|
||||||
|
.map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
|
||||||
|
|
||||||
let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
|
let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
|
||||||
|
|
||||||
handle_fixes(&snap, ¶ms, &mut res)?;
|
handle_fixes(&snap, ¶ms, &mut res)?;
|
||||||
|
@ -812,7 +832,7 @@ pub(crate) fn handle_code_action(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_resolve_code_action(
|
pub(crate) fn handle_resolve_code_action(
|
||||||
snap: GlobalStateSnapshot,
|
mut snap: GlobalStateSnapshot,
|
||||||
params: lsp_ext::ResolveCodeActionParams,
|
params: lsp_ext::ResolveCodeActionParams,
|
||||||
) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
|
) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
|
||||||
let _p = profile("handle_resolve_code_action");
|
let _p = profile("handle_resolve_code_action");
|
||||||
|
@ -821,6 +841,12 @@ pub(crate) fn handle_resolve_code_action(
|
||||||
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 };
|
||||||
|
|
||||||
|
snap.config.assist.allowed = 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)?;
|
||||||
let (id_string, index) = split_delim(¶ms.id, ':').unwrap();
|
let (id_string, index) = split_delim(¶ms.id, ':').unwrap();
|
||||||
let index = index.parse::<usize>().unwrap();
|
let index = index.parse::<usize>().unwrap();
|
||||||
|
|
Loading…
Reference in a new issue