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 { CodeActionOptions {
code_action_kinds: Some( code_action_kinds: Some(
SupportedCodeAction::all() SupportedCodeAction::all()
.flat_map(|action| action.kinds().into_iter()) .map(SupportedCodeAction::to_kind)
.collect(), .collect(),
), ),
work_done_progress_options: WorkDoneProgressOptions { work_done_progress_options: WorkDoneProgressOptions {
@ -284,18 +284,21 @@ pub(crate) enum SupportedCodeAction {
} }
impl SupportedCodeAction { impl SupportedCodeAction {
/// Returns the possible LSP code action kind(s) that map to this code action. /// Returns the LSP code action kind that map to this code action.
fn kinds(self) -> Vec<CodeActionKind> { fn to_kind(self) -> CodeActionKind {
match self { match self {
Self::QuickFix => vec![CodeActionKind::QUICKFIX], Self::QuickFix => CodeActionKind::QUICKFIX,
Self::SourceFixAll => vec![CodeActionKind::SOURCE_FIX_ALL, crate::SOURCE_FIX_ALL_RUFF], Self::SourceFixAll => crate::SOURCE_FIX_ALL_RUFF,
Self::SourceOrganizeImports => vec![ Self::SourceOrganizeImports => crate::SOURCE_ORGANIZE_IMPORTS_RUFF,
CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
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. /// Returns all code actions kinds that the server currently supports.
fn all() -> impl Iterator<Item = Self> { fn all() -> impl Iterator<Item = Self> {
[ [
@ -306,16 +309,3 @@ impl SupportedCodeAction {
.into_iter() .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, None,
) )
}; };
let action = types::CodeAction {
Ok(CodeActionOrCommand::CodeAction(types::CodeAction {
title: format!("{DIAGNOSTIC_NAME}: Fix all auto-fixable problems"), title: format!("{DIAGNOSTIC_NAME}: Fix all auto-fixable problems"),
kind: Some(types::CodeActionKind::SOURCE_FIX_ALL), kind: Some(crate::SOURCE_FIX_ALL_RUFF),
edit, edit,
data, data,
..Default::default() ..Default::default()
}; }))
Ok(types::CodeActionOrCommand::CodeAction(action))
} }
fn organize_imports(snapshot: &DocumentSnapshot) -> crate::Result<CodeActionOrCommand> { fn organize_imports(snapshot: &DocumentSnapshot) -> crate::Result<CodeActionOrCommand> {
@ -147,14 +147,14 @@ fn organize_imports(snapshot: &DocumentSnapshot) -> crate::Result<CodeActionOrCo
None, None,
) )
}; };
let action = types::CodeAction {
Ok(CodeActionOrCommand::CodeAction(types::CodeAction {
title: format!("{DIAGNOSTIC_NAME}: Organize imports"), title: format!("{DIAGNOSTIC_NAME}: Organize imports"),
kind: Some(types::CodeActionKind::SOURCE_ORGANIZE_IMPORTS), kind: Some(crate::SOURCE_ORGANIZE_IMPORTS_RUFF),
edit, edit,
data, data,
..Default::default() ..Default::default()
}; }))
Ok(types::CodeActionOrCommand::CodeAction(action))
} }
/// If `action_filter` is `None`, this returns [`SupportedCodeActionKind::all()`]. Otherwise, /// If `action_filter` is `None`, this returns [`SupportedCodeActionKind::all()`]. Otherwise,
@ -166,14 +166,8 @@ fn supported_code_actions(
return SupportedCodeAction::all().collect(); return SupportedCodeAction::all().collect();
}; };
SupportedCodeAction::all() action_filter
.filter(move |action| { .into_iter()
action_filter.iter().any(|filter| { .flat_map(SupportedCodeAction::from_kind)
action
.kinds()
.iter()
.any(|kind| kind.as_str().starts_with(filter.as_str()))
})
})
.collect() .collect()
} }

View file

@ -30,14 +30,23 @@ impl super::BackgroundDocumentRequestHandler for CodeActionResolve {
) -> Result<types::CodeAction> { ) -> Result<types::CodeAction> {
let document = snapshot.document(); let document = snapshot.document();
let action_kind: SupportedCodeAction = action let code_actions = SupportedCodeAction::from_kind(
action
.kind .kind
.clone() .clone()
.ok_or(anyhow::anyhow!("No kind was given for code action")) .ok_or(anyhow::anyhow!("No kind was given for code action"))
.with_failure_code(ErrorCode::InvalidParams)? .with_failure_code(ErrorCode::InvalidParams)?,
.try_into() )
.map_err(|()| anyhow::anyhow!("Code action was of an invalid kind")) .collect::<Vec<_>>();
.with_failure_code(ErrorCode::InvalidParams)?;
// 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 { action.edit = match action_kind {
SupportedCodeAction::SourceFixAll => Some( SupportedCodeAction::SourceFixAll => Some(