mirror of https://github.com/astral-sh/ruff
[ty] Add code action support to playground (#21655)
This commit is contained in:
parent
792ec3e96e
commit
761031f729
|
|
@ -4602,6 +4602,7 @@ dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
|
"ruff_diagnostics",
|
||||||
"ruff_notebook",
|
"ruff_notebook",
|
||||||
"ruff_python_formatter",
|
"ruff_python_formatter",
|
||||||
"ruff_source_file",
|
"ruff_source_file",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ ty_project = { workspace = true, default-features = false, features = [
|
||||||
ty_python_semantic = { workspace = true }
|
ty_python_semantic = { workspace = true }
|
||||||
|
|
||||||
ruff_db = { workspace = true, default-features = false, features = [] }
|
ruff_db = { workspace = true, default-features = false, features = [] }
|
||||||
|
ruff_diagnostics = { workspace = true }
|
||||||
ruff_notebook = { workspace = true }
|
ruff_notebook = { workspace = true }
|
||||||
ruff_python_formatter = { workspace = true }
|
ruff_python_formatter = { workspace = true }
|
||||||
ruff_source_file = { workspace = true }
|
ruff_source_file = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use ruff_db::system::{
|
||||||
SystemPath, SystemPathBuf, SystemVirtualPath, WritableSystem,
|
SystemPath, SystemPathBuf, SystemVirtualPath, WritableSystem,
|
||||||
};
|
};
|
||||||
use ruff_db::vendored::VendoredPath;
|
use ruff_db::vendored::VendoredPath;
|
||||||
|
use ruff_diagnostics::Applicability;
|
||||||
use ruff_notebook::Notebook;
|
use ruff_notebook::Notebook;
|
||||||
use ruff_python_formatter::formatted_file;
|
use ruff_python_formatter::formatted_file;
|
||||||
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
|
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
|
||||||
|
|
@ -732,6 +733,53 @@ impl Diagnostic {
|
||||||
.to_string()
|
.to_string()
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the code action for this diagnostic, if it has a fix.
|
||||||
|
#[wasm_bindgen(js_name = "codeAction")]
|
||||||
|
pub fn code_action(&self, workspace: &Workspace) -> Option<CodeAction> {
|
||||||
|
let fix = self
|
||||||
|
.inner
|
||||||
|
.fix()
|
||||||
|
.filter(|fix| fix.applies(Applicability::Unsafe))?;
|
||||||
|
|
||||||
|
let primary_span = self.inner.primary_span()?;
|
||||||
|
let file = primary_span.expect_ty_file();
|
||||||
|
|
||||||
|
let source = source_text(&workspace.db, file);
|
||||||
|
let index = line_index(&workspace.db, file);
|
||||||
|
|
||||||
|
let edits: Vec<TextEdit> = fix
|
||||||
|
.edits()
|
||||||
|
.iter()
|
||||||
|
.map(|edit| TextEdit {
|
||||||
|
range: Range::from_text_range(
|
||||||
|
edit.range(),
|
||||||
|
&index,
|
||||||
|
&source,
|
||||||
|
workspace.position_encoding,
|
||||||
|
),
|
||||||
|
new_text: edit.content().unwrap_or_default().to_string(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let title = self
|
||||||
|
.inner
|
||||||
|
.first_help_text()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.unwrap_or_else(|| format!("Fix {}", self.inner.id()));
|
||||||
|
|
||||||
|
Some(CodeAction { title, edits })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A code action that can be applied to fix a diagnostic.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct CodeAction {
|
||||||
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
|
pub title: String,
|
||||||
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
|
pub edits: Vec<TextEdit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,7 @@ function useCheckResult(
|
||||||
severity: diagnostic.severity(),
|
severity: diagnostic.severity(),
|
||||||
range: diagnostic.toRange(workspace) ?? null,
|
range: diagnostic.toRange(workspace) ?? null,
|
||||||
textRange: diagnostic.textRange() ?? null,
|
textRange: diagnostic.textRange() ?? null,
|
||||||
|
raw: diagnostic,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
import type { Severity, Range, TextRange } from "ty_wasm";
|
import type {
|
||||||
|
Severity,
|
||||||
|
Range,
|
||||||
|
TextRange,
|
||||||
|
Diagnostic as TyDiagnostic,
|
||||||
|
} from "ty_wasm";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Theme } from "shared";
|
import { Theme } from "shared";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
@ -103,4 +108,5 @@ export interface Diagnostic {
|
||||||
severity: Severity;
|
severity: Severity;
|
||||||
range: Range | null;
|
range: Range | null;
|
||||||
textRange: TextRange | null;
|
textRange: TextRange | null;
|
||||||
|
raw: TyDiagnostic;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,8 +189,11 @@ class PlaygroundServer
|
||||||
languages.DocumentSemanticTokensProvider,
|
languages.DocumentSemanticTokensProvider,
|
||||||
languages.DocumentRangeSemanticTokensProvider,
|
languages.DocumentRangeSemanticTokensProvider,
|
||||||
languages.SignatureHelpProvider,
|
languages.SignatureHelpProvider,
|
||||||
languages.DocumentHighlightProvider
|
languages.DocumentHighlightProvider,
|
||||||
|
languages.CodeActionProvider
|
||||||
{
|
{
|
||||||
|
private diagnostics: Diagnostic[] = [];
|
||||||
|
|
||||||
private typeDefinitionProviderDisposable: IDisposable;
|
private typeDefinitionProviderDisposable: IDisposable;
|
||||||
private declarationProviderDisposable: IDisposable;
|
private declarationProviderDisposable: IDisposable;
|
||||||
private definitionProviderDisposable: IDisposable;
|
private definitionProviderDisposable: IDisposable;
|
||||||
|
|
@ -204,6 +207,7 @@ class PlaygroundServer
|
||||||
private rangeSemanticTokensDisposable: IDisposable;
|
private rangeSemanticTokensDisposable: IDisposable;
|
||||||
private signatureHelpDisposable: IDisposable;
|
private signatureHelpDisposable: IDisposable;
|
||||||
private documentHighlightDisposable: IDisposable;
|
private documentHighlightDisposable: IDisposable;
|
||||||
|
private codeActionDisposable: IDisposable;
|
||||||
private inVendoredFileCondition: editor.IContextKey<boolean>;
|
private inVendoredFileCondition: editor.IContextKey<boolean>;
|
||||||
// Cache for vendored file handles
|
// Cache for vendored file handles
|
||||||
private vendoredFileHandles = new Map<string, FileHandle>();
|
private vendoredFileHandles = new Map<string, FileHandle>();
|
||||||
|
|
@ -253,6 +257,10 @@ class PlaygroundServer
|
||||||
monaco.languages.registerSignatureHelpProvider("python", this);
|
monaco.languages.registerSignatureHelpProvider("python", this);
|
||||||
this.documentHighlightDisposable =
|
this.documentHighlightDisposable =
|
||||||
monaco.languages.registerDocumentHighlightProvider("python", this);
|
monaco.languages.registerDocumentHighlightProvider("python", this);
|
||||||
|
this.codeActionDisposable = monaco.languages.registerCodeActionProvider(
|
||||||
|
"python",
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
|
||||||
this.inVendoredFileCondition = editor.createContextKey<boolean>(
|
this.inVendoredFileCondition = editor.createContextKey<boolean>(
|
||||||
"inVendoredFile",
|
"inVendoredFile",
|
||||||
|
|
@ -534,6 +542,8 @@ class PlaygroundServer
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDiagnostics(diagnostics: Array<Diagnostic>) {
|
updateDiagnostics(diagnostics: Array<Diagnostic>) {
|
||||||
|
this.diagnostics = diagnostics;
|
||||||
|
|
||||||
if (this.props.files.selected == null) {
|
if (this.props.files.selected == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -584,6 +594,59 @@ class PlaygroundServer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provideCodeActions(
|
||||||
|
model: editor.ITextModel,
|
||||||
|
range: Range,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_context: languages.CodeActionContext,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_token: CancellationToken,
|
||||||
|
): languages.ProviderResult<languages.CodeActionList> {
|
||||||
|
const actions: languages.CodeAction[] = [];
|
||||||
|
|
||||||
|
for (const diagnostic of this.diagnostics) {
|
||||||
|
const diagnosticRange = diagnostic.range;
|
||||||
|
if (diagnosticRange == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const monacoRange = tyRangeToMonacoRange(diagnosticRange);
|
||||||
|
if (!Range.areIntersecting(range, monacoRange)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeAction = diagnostic.raw.codeAction(this.props.workspace);
|
||||||
|
if (codeAction == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.push({
|
||||||
|
title: codeAction.title,
|
||||||
|
kind: "quickfix",
|
||||||
|
isPreferred: true,
|
||||||
|
edit: {
|
||||||
|
edits: codeAction.edits.map((edit) => ({
|
||||||
|
resource: model.uri,
|
||||||
|
textEdit: {
|
||||||
|
range: tyRangeToMonacoRange(edit.range),
|
||||||
|
text: edit.new_text,
|
||||||
|
},
|
||||||
|
versionId: model.getVersionId(),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions,
|
||||||
|
dispose: () => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
provideHover(
|
provideHover(
|
||||||
model: editor.ITextModel,
|
model: editor.ITextModel,
|
||||||
position: Position,
|
position: Position,
|
||||||
|
|
@ -836,6 +899,7 @@ class PlaygroundServer
|
||||||
this.completionDisposable.dispose();
|
this.completionDisposable.dispose();
|
||||||
this.signatureHelpDisposable.dispose();
|
this.signatureHelpDisposable.dispose();
|
||||||
this.documentHighlightDisposable.dispose();
|
this.documentHighlightDisposable.dispose();
|
||||||
|
this.codeActionDisposable.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue