Enable ruff-specific source actions (#10916)

## Summary

Fixes #10780.

The server now send code actions to the client with a Ruff-specific
kind, `source.*.ruff`. The kind filtering logic has also been reworked
to support this.

## Test Plan

Add this to your `settings.json` in VS Code:

```json
{
  "[python]": {
    "editor.codeActionsOnSave": {
      "source.organizeImports.ruff": "explicit",
    },
  }
}
```

Imports should be automatically organized when you manually save with
`Ctrl/Cmd+S`.
This commit is contained in:
Jane Lewis 2024-04-16 11:21:08 -07:00 committed by GitHub
parent cffc55576f
commit eab3c4e334
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 40 additions and 47 deletions

View file

@ -224,7 +224,7 @@ impl Server {
CodeActionOptions {
code_action_kinds: Some(
SupportedCodeAction::all()
.flat_map(|action| action.kinds().into_iter())
.map(SupportedCodeAction::to_kind)
.collect(),
),
work_done_progress_options: WorkDoneProgressOptions {
@ -284,18 +284,21 @@ pub(crate) enum SupportedCodeAction {
}
impl SupportedCodeAction {
/// Returns the possible LSP code action kind(s) that map to this code action.
fn kinds(self) -> Vec<CodeActionKind> {
/// Returns the LSP code action kind that map to this code action.
fn to_kind(self) -> CodeActionKind {
match self {
Self::QuickFix => vec![CodeActionKind::QUICKFIX],
Self::SourceFixAll => vec![CodeActionKind::SOURCE_FIX_ALL, crate::SOURCE_FIX_ALL_RUFF],
Self::SourceOrganizeImports => vec![
CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
crate::SOURCE_ORGANIZE_IMPORTS_RUFF,
],
Self::QuickFix => CodeActionKind::QUICKFIX,
Self::SourceFixAll => crate::SOURCE_FIX_ALL_RUFF,
Self::SourceOrganizeImports => crate::SOURCE_ORGANIZE_IMPORTS_RUFF,
}
}
fn from_kind(kind: CodeActionKind) -> impl Iterator<Item = Self> {
Self::all().filter(move |supported_kind| {
supported_kind.to_kind().as_str().starts_with(kind.as_str())
})
}
/// Returns all code actions kinds that the server currently supports.
fn all() -> impl Iterator<Item = Self> {
[
@ -306,16 +309,3 @@ impl SupportedCodeAction {
.into_iter()
}
}
impl TryFrom<CodeActionKind> for SupportedCodeAction {
type Error = ();
fn try_from(kind: CodeActionKind) -> std::result::Result<Self, Self::Error> {
for supported_kind in Self::all() {
if supported_kind.kinds().contains(&kind) {
return Ok(supported_kind);
}
}
Err(())
}
}

View file

@ -112,14 +112,14 @@ fn fix_all(snapshot: &DocumentSnapshot) -> crate::Result<CodeActionOrCommand> {
None,
)
};
let action = types::CodeAction {
Ok(CodeActionOrCommand::CodeAction(types::CodeAction {
title: format!("{DIAGNOSTIC_NAME}: Fix all auto-fixable problems"),
kind: Some(types::CodeActionKind::SOURCE_FIX_ALL),
kind: Some(crate::SOURCE_FIX_ALL_RUFF),
edit,
data,
..Default::default()
};
Ok(types::CodeActionOrCommand::CodeAction(action))
}))
}
fn organize_imports(snapshot: &DocumentSnapshot) -> crate::Result<CodeActionOrCommand> {
@ -147,14 +147,14 @@ fn organize_imports(snapshot: &DocumentSnapshot) -> crate::Result<CodeActionOrCo
None,
)
};
let action = types::CodeAction {
Ok(CodeActionOrCommand::CodeAction(types::CodeAction {
title: format!("{DIAGNOSTIC_NAME}: Organize imports"),
kind: Some(types::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
kind: Some(crate::SOURCE_ORGANIZE_IMPORTS_RUFF),
edit,
data,
..Default::default()
};
Ok(types::CodeActionOrCommand::CodeAction(action))
}))
}
/// If `action_filter` is `None`, this returns [`SupportedCodeActionKind::all()`]. Otherwise,
@ -166,14 +166,8 @@ fn supported_code_actions(
return SupportedCodeAction::all().collect();
};
SupportedCodeAction::all()
.filter(move |action| {
action_filter.iter().any(|filter| {
action
.kinds()
.iter()
.any(|kind| kind.as_str().starts_with(filter.as_str()))
})
})
action_filter
.into_iter()
.flat_map(SupportedCodeAction::from_kind)
.collect()
}

View file

@ -30,14 +30,23 @@ impl super::BackgroundDocumentRequestHandler for CodeActionResolve {
) -> Result<types::CodeAction> {
let document = snapshot.document();
let action_kind: SupportedCodeAction = action
.kind
.clone()
.ok_or(anyhow::anyhow!("No kind was given for code action"))
.with_failure_code(ErrorCode::InvalidParams)?
.try_into()
.map_err(|()| anyhow::anyhow!("Code action was of an invalid kind"))
.with_failure_code(ErrorCode::InvalidParams)?;
let code_actions = SupportedCodeAction::from_kind(
action
.kind
.clone()
.ok_or(anyhow::anyhow!("No kind was given for code action"))
.with_failure_code(ErrorCode::InvalidParams)?,
)
.collect::<Vec<_>>();
// Ensure that the code action maps to _exactly one_ supported code action
let [action_kind] = code_actions.as_slice() else {
return Err(anyhow::anyhow!(
"Code action resolver did not expect code action kind {:?}",
action.kind.as_ref().unwrap()
))
.with_failure_code(ErrorCode::InvalidParams);
};
action.edit = match action_kind {
SupportedCodeAction::SourceFixAll => Some(