mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-16 08:30:16 +00:00
Implement tab autocomplete for ruff config
(#15603)
## Summary Not the most important feature, but hey... was marked as the good first issue ;-) fixes #4551 Unfortunately, looks like clap only generates proper completions for zsh, so this would not make any difference for bash/fish. ## Test Plan - cargo nextest run - manual test by sourcing completions and then triggering autocomplete: ```shell misha@PandaBook ruff % source <(target/debug/ruff generate-shell-completion zsh) misha@PandaBook ruff % target/debug/ruff config lin line-length -- The line length to use when enforcing long-lines violations lint -- Configures how Ruff checks your code. lint.allowed-confusables -- A list of allowed 'confusable' Unicode characters to ignore lint.dummy-variable-rgx -- A regular expression used to identify 'dummy' variables, or lint.exclude -- A list of file patterns to exclude from linting in addition lint.explicit-preview-rules -- Whether to require exact codes to select preview rules. Whe lint.extend-fixable -- A list of rule codes or prefixes to consider fixable, in ad lint.extend-ignore -- A list of rule codes or prefixes to ignore, in addition to lint.extend-per-file-ignores -- A list of mappings from file pattern to rule codes or prefi lint.extend-safe-fixes -- A list of rule codes or prefixes for which unsafe fixes sho lint.extend-select -- A list of rule codes or prefixes to enable, in addition to lint.extend-unsafe-fixes -- A list of rule codes or prefixes for which safe fixes shoul lint.external -- A list of rule codes or prefixes that are unsupported by Ru lint.fixable -- A list of rule codes or prefixes to consider fixable. By de lint.flake8-annotations -- Print a list of available options lint.flake8-annotations.allow-star-arg-any -- Whether to suppress `ANN401` for dynamically typed `*args` ... ``` - check command help ```shell ❯ target/debug/ruff config -h List or describe the available configuration options Usage: ruff config [OPTIONS] [OPTION] Arguments: [OPTION] Config key to show Options: --output-format <OUTPUT_FORMAT> Output format [default: text] [possible values: text, json] -h, --help Print help Log levels: -v, --verbose Enable verbose logging -q, --quiet Print diagnostics, but nothing else -s, --silent Disable all logging (but still exit with status code "1" upon detecting diagnostics) Global options: --config <CONFIG_OPTION> Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might find in a `ruff.toml` configuration file) overriding a specific configuration option. Overrides of individual settings using this option always take precedence over all configuration files, including configuration files that were also specified using `--config` --isolated Ignore all configuration files ``` - running original command ```shell ❯ target/debug/ruff config cache-dir extend output-format fix unsafe-fixes fix-only show-fixes required-version preview exclude extend-exclude extend-include force-exclude include respect-gitignore builtins namespace-packages target-version src line-length indent-width lint format analyze ```
This commit is contained in:
parent
0a2139f496
commit
646f1942aa
4 changed files with 168 additions and 1 deletions
|
@ -30,6 +30,8 @@ use ruff_workspace::resolver::ConfigurationTransformer;
|
|||
use rustc_hash::FxHashMap;
|
||||
use toml;
|
||||
|
||||
use crate::commands::completions::config::{OptionString, OptionStringParser};
|
||||
|
||||
/// All configuration options that can be passed "globally",
|
||||
/// i.e., can be passed to all subcommands
|
||||
#[derive(Debug, Default, Clone, clap::Args)]
|
||||
|
@ -114,7 +116,11 @@ pub enum Command {
|
|||
/// List or describe the available configuration options.
|
||||
Config {
|
||||
/// Config key to show
|
||||
option: Option<String>,
|
||||
#[arg(
|
||||
value_parser = OptionStringParser,
|
||||
hide_possible_values = true
|
||||
)]
|
||||
option: Option<OptionString>,
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
output_format: HelpFormat,
|
||||
|
|
158
crates/ruff/src/commands/completions/config.rs
Normal file
158
crates/ruff/src/commands/completions/config.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
|
||||
use itertools::Itertools;
|
||||
use std::str::FromStr;
|
||||
|
||||
use ruff_workspace::{
|
||||
options::Options,
|
||||
options_base::{OptionField, OptionSet, OptionsMetadata, Visit},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct CollectOptionsVisitor {
|
||||
values: Vec<(String, String)>,
|
||||
parents: Vec<String>,
|
||||
}
|
||||
|
||||
impl IntoIterator for CollectOptionsVisitor {
|
||||
type Item = (String, String);
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.values.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for CollectOptionsVisitor {
|
||||
fn record_set(&mut self, name: &str, group: OptionSet) {
|
||||
let fully_qualified_name = self
|
||||
.parents
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.chain(std::iter::once(name))
|
||||
.collect::<Vec<_>>()
|
||||
.join(".");
|
||||
|
||||
// Only add the set to completion list if it has it's own documentation.
|
||||
self.values.push((
|
||||
fully_qualified_name,
|
||||
group.documentation().unwrap_or("").to_owned(),
|
||||
));
|
||||
|
||||
self.parents.push(name.to_owned());
|
||||
group.record(self);
|
||||
self.parents.pop();
|
||||
}
|
||||
|
||||
fn record_field(&mut self, name: &str, field: OptionField) {
|
||||
let fqn = self
|
||||
.parents
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.chain(std::iter::once(name))
|
||||
.collect::<Vec<_>>()
|
||||
.join(".");
|
||||
|
||||
self.values.push((fqn, field.doc.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque type used solely to enable tab completions
|
||||
/// for `ruff option [OPTION]` command.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OptionString(String);
|
||||
|
||||
impl From<String> for OptionString {
|
||||
fn from(s: String) -> Self {
|
||||
OptionString(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OptionString> for String {
|
||||
fn from(value: OptionString) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for OptionString {
|
||||
fn from(s: &str) -> Self {
|
||||
OptionString(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for OptionString {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for OptionString {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Options::metadata()
|
||||
.has(s)
|
||||
.then(|| OptionString(s.to_owned()))
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OptionStringParser;
|
||||
|
||||
impl ValueParserFactory for OptionString {
|
||||
type Parser = OptionStringParser;
|
||||
|
||||
fn value_parser() -> Self::Parser {
|
||||
OptionStringParser
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedValueParser for OptionStringParser {
|
||||
type Value = OptionString;
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
cmd: &clap::Command,
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
let value = value
|
||||
.to_str()
|
||||
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||
|
||||
value.parse().map_err(|()| {
|
||||
let mut error = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
error.insert(
|
||||
clap::error::ContextKind::InvalidArg,
|
||||
clap::error::ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
error.insert(
|
||||
clap::error::ContextKind::InvalidValue,
|
||||
clap::error::ContextValue::String(value.to_string()),
|
||||
);
|
||||
error
|
||||
})
|
||||
}
|
||||
|
||||
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||
let mut visitor = CollectOptionsVisitor::default();
|
||||
Options::metadata().record(&mut visitor);
|
||||
|
||||
Some(Box::new(visitor.into_iter().map(|(name, doc)| {
|
||||
let first_paragraph = doc
|
||||
.lines()
|
||||
.take_while(|line| !line.trim_end().is_empty())
|
||||
// Replace double quotes with single quotes,to avoid clap's lack of escaping
|
||||
// when creating zsh completions. This has no security implications, as it only
|
||||
// affects the help string, which is never executed
|
||||
.map(|s| s.replace('"', "'"))
|
||||
.join(" ");
|
||||
|
||||
PossibleValue::new(name).help(first_paragraph)
|
||||
})))
|
||||
}
|
||||
}
|
1
crates/ruff/src/commands/completions/mod.rs
Normal file
1
crates/ruff/src/commands/completions/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub(crate) mod config;
|
|
@ -1,3 +1,5 @@
|
|||
pub(crate) mod completions;
|
||||
|
||||
pub(crate) mod add_noqa;
|
||||
pub(crate) mod analyze_graph;
|
||||
pub(crate) mod check;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue