Skip to content

Commit

Permalink
implement tab autocomplete for ruff config. fixes astral-sh#4551
Browse files Browse the repository at this point in the history
  • Loading branch information
mishamsk committed Jan 20, 2025
1 parent 98fccec commit 5289aaa
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/ruff/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ruff_python_formatter = { workspace = true }
ruff_server = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
ruff_workspace = { workspace = true }
ruff_workspace = { workspace = true, features = ["clap"] }

anyhow = { workspace = true }
argfile = { workspace = true }
Expand Down
7 changes: 6 additions & 1 deletion crates/ruff/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use ruff_linter::settings::types::{
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
use ruff_source_file::{LineIndex, OneIndexed};
use ruff_text_size::TextRange;
use ruff_workspace::cli::clap_completion::{OptionString, OptionStringParser};
use ruff_workspace::configuration::{Configuration, RuleSelection};
use ruff_workspace::options::{Options, PycodestyleOptions};
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
Expand Down Expand Up @@ -114,7 +115,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 = false
)]
option: Option<OptionString>,
/// Output format
#[arg(long, value_enum, default_value = "text")]
output_format: HelpFormat,
Expand Down
10 changes: 8 additions & 2 deletions crates/ruff_workspace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_formatter = { workspace = true, features = ["serde"] }
ruff_python_semantic = { workspace = true, features = ["serde"] }
ruff_python_stdlib = {workspace = true}
ruff_python_stdlib = { workspace = true }
ruff_source_file = { workspace = true }

anyhow = { workspace = true }
Expand All @@ -43,6 +43,7 @@ serde = { workspace = true }
shellexpand = { workspace = true }
strum = { workspace = true }
toml = { workspace = true }
clap = { workspace = true, features = ["derive", "string"], optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
etcetera = { workspace = true }
Expand All @@ -58,7 +59,12 @@ ignored = ["colored"]

[features]
default = []
schemars = ["dep:schemars", "ruff_formatter/schemars", "ruff_python_formatter/schemars", "ruff_python_semantic/schemars"]
schemars = [
"dep:schemars",
"ruff_formatter/schemars",
"ruff_python_formatter/schemars",
"ruff_python_semantic/schemars",
]

[lints]
workspace = true
156 changes: 156 additions & 0 deletions crates/ruff_workspace/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#[cfg(feature = "clap")]
pub mod clap_completion {
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
use std::str::FromStr;

use crate::{
options::Options,
options_base::{OptionField, OptionSet, OptionsMetadata, Visit},
};

#[derive(Default)]
struct CollectOptionsVisitor {
values: Vec<(String, String)>,
parents: Vec<String>,
}

impl IntoIterator for CollectOptionsVisitor {
type IntoIter = std::vec::IntoIter<(String, String)>;
type Item = (String, String);

fn into_iter(self) -> Self::IntoIter {
self.values.into_iter()
}
}

impl Visit for CollectOptionsVisitor {
fn record_set(&mut self, name: &str, group: OptionSet) {
let fqn = self
.parents
.iter()
.map(String::as_str)
.chain(std::iter::once(name))
.collect::<Vec<_>>()
.join(".");

self.values
.push((fqn, group.documentation().unwrap_or_default().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 for option strings on the command line.
#[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(o: OptionString) -> Self {
o.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_line = doc
.lines()
.next()
.unwrap_or("")
.chars()
.take(80)
.collect::<String>();

PossibleValue::new(name).help(first_line)
})))
}
}
}
2 changes: 2 additions & 0 deletions crates/ruff_workspace/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "clap")]
pub mod cli;
pub mod configuration;
pub mod options;
pub mod pyproject;
Expand Down

0 comments on commit 5289aaa

Please sign in to comment.