mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-06-04 05:57:39 +02:00
391 lines
13 KiB
Rust
391 lines
13 KiB
Rust
use std::error;
|
|
use std::fmt;
|
|
use std::str::FromStr;
|
|
|
|
use termcolor::{Color, ColorSpec, ParseColorError};
|
|
|
|
/// Returns a default set of color specifications.
|
|
///
|
|
/// This may change over time, but the color choices are meant to be fairly
|
|
/// conservative that work across terminal themes.
|
|
///
|
|
/// Additional color specifications can be added to the list returned. More
|
|
/// recently added specifications override previously added specifications.
|
|
pub fn default_color_specs() -> Vec<UserColorSpec> {
|
|
vec![
|
|
#[cfg(unix)]
|
|
"path:fg:magenta".parse().unwrap(),
|
|
#[cfg(windows)]
|
|
"path:fg:cyan".parse().unwrap(),
|
|
"line:fg:green".parse().unwrap(),
|
|
"match:fg:red".parse().unwrap(),
|
|
"match:style:bold".parse().unwrap(),
|
|
]
|
|
}
|
|
|
|
/// An error that can occur when parsing color specifications.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub enum ColorError {
|
|
/// This occurs when an unrecognized output type is used.
|
|
UnrecognizedOutType(String),
|
|
/// This occurs when an unrecognized spec type is used.
|
|
UnrecognizedSpecType(String),
|
|
/// This occurs when an unrecognized color name is used.
|
|
UnrecognizedColor(String, String),
|
|
/// This occurs when an unrecognized style attribute is used.
|
|
UnrecognizedStyle(String),
|
|
/// This occurs when the format of a color specification is invalid.
|
|
InvalidFormat(String),
|
|
}
|
|
|
|
impl error::Error for ColorError {
|
|
fn description(&self) -> &str {
|
|
match *self {
|
|
ColorError::UnrecognizedOutType(_) => "unrecognized output type",
|
|
ColorError::UnrecognizedSpecType(_) => "unrecognized spec type",
|
|
ColorError::UnrecognizedColor(_, _) => "unrecognized color name",
|
|
ColorError::UnrecognizedStyle(_) => "unrecognized style attribute",
|
|
ColorError::InvalidFormat(_) => "invalid color spec",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ColorError {
|
|
fn from_parse_error(err: ParseColorError) -> ColorError {
|
|
ColorError::UnrecognizedColor(
|
|
err.invalid().to_string(),
|
|
err.to_string(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ColorError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match *self {
|
|
ColorError::UnrecognizedOutType(ref name) => write!(
|
|
f,
|
|
"unrecognized output type '{}'. Choose from: \
|
|
path, line, column, match.",
|
|
name,
|
|
),
|
|
ColorError::UnrecognizedSpecType(ref name) => write!(
|
|
f,
|
|
"unrecognized spec type '{}'. Choose from: \
|
|
fg, bg, style, none.",
|
|
name,
|
|
),
|
|
ColorError::UnrecognizedColor(_, ref msg) => write!(f, "{}", msg),
|
|
ColorError::UnrecognizedStyle(ref name) => write!(
|
|
f,
|
|
"unrecognized style attribute '{}'. Choose from: \
|
|
nobold, bold, nointense, intense, nounderline, \
|
|
underline.",
|
|
name,
|
|
),
|
|
ColorError::InvalidFormat(ref original) => write!(
|
|
f,
|
|
"invalid color spec format: '{}'. Valid format \
|
|
is '(path|line|column|match):(fg|bg|style):(value)'.",
|
|
original,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A merged set of color specifications.
|
|
///
|
|
/// This set of color specifications represents the various color types that
|
|
/// are supported by the printers in this crate. A set of color specifications
|
|
/// can be created from a sequence of
|
|
/// [`UserColorSpec`s](struct.UserColorSpec.html).
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct ColorSpecs {
|
|
path: ColorSpec,
|
|
line: ColorSpec,
|
|
column: ColorSpec,
|
|
matched: ColorSpec,
|
|
}
|
|
|
|
/// A single color specification provided by the user.
|
|
///
|
|
/// ## Format
|
|
///
|
|
/// The format of a `Spec` is a triple: `{type}:{attribute}:{value}`. Each
|
|
/// component is defined as follows:
|
|
///
|
|
/// * `{type}` can be one of `path`, `line`, `column` or `match`.
|
|
/// * `{attribute}` can be one of `fg`, `bg` or `style`. `{attribute}` may also
|
|
/// be the special value `none`, in which case, `{value}` can be omitted.
|
|
/// * `{value}` is either a color name (for `fg`/`bg`) or a style instruction.
|
|
///
|
|
/// `{type}` controls which part of the output should be styled.
|
|
///
|
|
/// When `{attribute}` is `none`, then this should cause any existing style
|
|
/// settings to be cleared for the specified `type`.
|
|
///
|
|
/// `{value}` should be a color when `{attribute}` is `fg` or `bg`, or it
|
|
/// should be a style instruction when `{attribute}` is `style`. When
|
|
/// `{attribute}` is `none`, `{value}` must be omitted.
|
|
///
|
|
/// Valid colors are `black`, `blue`, `green`, `red`, `cyan`, `magenta`,
|
|
/// `yellow`, `white`. Extended colors can also be specified, and are formatted
|
|
/// as `x` (for 256-bit colors) or `x,x,x` (for 24-bit true color), where
|
|
/// `x` is a number between 0 and 255 inclusive. `x` may be given as a normal
|
|
/// decimal number of a hexadecimal number, where the latter is prefixed by
|
|
/// `0x`.
|
|
///
|
|
/// Valid style instructions are `nobold`, `bold`, `intense`, `nointense`,
|
|
/// `underline`, `nounderline`.
|
|
///
|
|
/// ## Example
|
|
///
|
|
/// The standard way to build a `UserColorSpec` is to parse it from a string.
|
|
/// Once multiple `UserColorSpec`s have been constructed, they can be provided
|
|
/// to the standard printer where they will automatically be applied to the
|
|
/// output.
|
|
///
|
|
/// A `UserColorSpec` can also be converted to a `termcolor::ColorSpec`:
|
|
///
|
|
/// ```rust
|
|
/// # fn main() {
|
|
/// use termcolor::{Color, ColorSpec};
|
|
/// use grep_printer::UserColorSpec;
|
|
///
|
|
/// let user_spec1: UserColorSpec = "path:fg:blue".parse().unwrap();
|
|
/// let user_spec2: UserColorSpec = "match:bg:0xff,0x7f,0x00".parse().unwrap();
|
|
///
|
|
/// let spec1 = user_spec1.to_color_spec();
|
|
/// let spec2 = user_spec2.to_color_spec();
|
|
///
|
|
/// assert_eq!(spec1.fg(), Some(&Color::Blue));
|
|
/// assert_eq!(spec2.bg(), Some(&Color::Rgb(0xFF, 0x7F, 0x00)));
|
|
/// # }
|
|
/// ```
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct UserColorSpec {
|
|
ty: OutType,
|
|
value: SpecValue,
|
|
}
|
|
|
|
impl UserColorSpec {
|
|
/// Convert this user provided color specification to a specification that
|
|
/// can be used with `termcolor`. This drops the type of this specification
|
|
/// (where the type indicates where the color is applied in the standard
|
|
/// printer, e.g., to the file path or the line numbers, etc.).
|
|
pub fn to_color_spec(&self) -> ColorSpec {
|
|
let mut spec = ColorSpec::default();
|
|
self.value.merge_into(&mut spec);
|
|
spec
|
|
}
|
|
}
|
|
|
|
/// The actual value given by the specification.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
enum SpecValue {
|
|
None,
|
|
Fg(Color),
|
|
Bg(Color),
|
|
Style(Style),
|
|
}
|
|
|
|
/// The set of configurable portions of ripgrep's output.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
enum OutType {
|
|
Path,
|
|
Line,
|
|
Column,
|
|
Match,
|
|
}
|
|
|
|
/// The specification type.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
enum SpecType {
|
|
Fg,
|
|
Bg,
|
|
Style,
|
|
None,
|
|
}
|
|
|
|
/// The set of available styles for use in the terminal.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
enum Style {
|
|
Bold,
|
|
NoBold,
|
|
Intense,
|
|
NoIntense,
|
|
Underline,
|
|
NoUnderline,
|
|
}
|
|
|
|
impl ColorSpecs {
|
|
/// Create color specifications from a list of user supplied
|
|
/// specifications.
|
|
pub fn new(specs: &[UserColorSpec]) -> ColorSpecs {
|
|
let mut merged = ColorSpecs::default();
|
|
for spec in specs {
|
|
match spec.ty {
|
|
OutType::Path => spec.merge_into(&mut merged.path),
|
|
OutType::Line => spec.merge_into(&mut merged.line),
|
|
OutType::Column => spec.merge_into(&mut merged.column),
|
|
OutType::Match => spec.merge_into(&mut merged.matched),
|
|
}
|
|
}
|
|
merged
|
|
}
|
|
|
|
/// Create a default set of specifications that have color.
|
|
///
|
|
/// This is distinct from `ColorSpecs`'s `Default` implementation in that
|
|
/// this provides a set of default color choices, where as the `Default`
|
|
/// implementation provides no color choices.
|
|
pub fn default_with_color() -> ColorSpecs {
|
|
ColorSpecs::new(&default_color_specs())
|
|
}
|
|
|
|
/// Return the color specification for coloring file paths.
|
|
pub fn path(&self) -> &ColorSpec {
|
|
&self.path
|
|
}
|
|
|
|
/// Return the color specification for coloring line numbers.
|
|
pub fn line(&self) -> &ColorSpec {
|
|
&self.line
|
|
}
|
|
|
|
/// Return the color specification for coloring column numbers.
|
|
pub fn column(&self) -> &ColorSpec {
|
|
&self.column
|
|
}
|
|
|
|
/// Return the color specification for coloring matched text.
|
|
pub fn matched(&self) -> &ColorSpec {
|
|
&self.matched
|
|
}
|
|
}
|
|
|
|
impl UserColorSpec {
|
|
/// Merge this spec into the given color specification.
|
|
fn merge_into(&self, cspec: &mut ColorSpec) {
|
|
self.value.merge_into(cspec);
|
|
}
|
|
}
|
|
|
|
impl SpecValue {
|
|
/// Merge this spec value into the given color specification.
|
|
fn merge_into(&self, cspec: &mut ColorSpec) {
|
|
match *self {
|
|
SpecValue::None => cspec.clear(),
|
|
SpecValue::Fg(ref color) => {
|
|
cspec.set_fg(Some(color.clone()));
|
|
}
|
|
SpecValue::Bg(ref color) => {
|
|
cspec.set_bg(Some(color.clone()));
|
|
}
|
|
SpecValue::Style(ref style) => match *style {
|
|
Style::Bold => {
|
|
cspec.set_bold(true);
|
|
}
|
|
Style::NoBold => {
|
|
cspec.set_bold(false);
|
|
}
|
|
Style::Intense => {
|
|
cspec.set_intense(true);
|
|
}
|
|
Style::NoIntense => {
|
|
cspec.set_intense(false);
|
|
}
|
|
Style::Underline => {
|
|
cspec.set_underline(true);
|
|
}
|
|
Style::NoUnderline => {
|
|
cspec.set_underline(false);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for UserColorSpec {
|
|
type Err = ColorError;
|
|
|
|
fn from_str(s: &str) -> Result<UserColorSpec, ColorError> {
|
|
let pieces: Vec<&str> = s.split(':').collect();
|
|
if pieces.len() <= 1 || pieces.len() > 3 {
|
|
return Err(ColorError::InvalidFormat(s.to_string()));
|
|
}
|
|
let otype: OutType = pieces[0].parse()?;
|
|
match pieces[1].parse()? {
|
|
SpecType::None => {
|
|
Ok(UserColorSpec { ty: otype, value: SpecValue::None })
|
|
}
|
|
SpecType::Style => {
|
|
if pieces.len() < 3 {
|
|
return Err(ColorError::InvalidFormat(s.to_string()));
|
|
}
|
|
let style: Style = pieces[2].parse()?;
|
|
Ok(UserColorSpec { ty: otype, value: SpecValue::Style(style) })
|
|
}
|
|
SpecType::Fg => {
|
|
if pieces.len() < 3 {
|
|
return Err(ColorError::InvalidFormat(s.to_string()));
|
|
}
|
|
let color: Color =
|
|
pieces[2].parse().map_err(ColorError::from_parse_error)?;
|
|
Ok(UserColorSpec { ty: otype, value: SpecValue::Fg(color) })
|
|
}
|
|
SpecType::Bg => {
|
|
if pieces.len() < 3 {
|
|
return Err(ColorError::InvalidFormat(s.to_string()));
|
|
}
|
|
let color: Color =
|
|
pieces[2].parse().map_err(ColorError::from_parse_error)?;
|
|
Ok(UserColorSpec { ty: otype, value: SpecValue::Bg(color) })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for OutType {
|
|
type Err = ColorError;
|
|
|
|
fn from_str(s: &str) -> Result<OutType, ColorError> {
|
|
match &*s.to_lowercase() {
|
|
"path" => Ok(OutType::Path),
|
|
"line" => Ok(OutType::Line),
|
|
"column" => Ok(OutType::Column),
|
|
"match" => Ok(OutType::Match),
|
|
_ => Err(ColorError::UnrecognizedOutType(s.to_string())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for SpecType {
|
|
type Err = ColorError;
|
|
|
|
fn from_str(s: &str) -> Result<SpecType, ColorError> {
|
|
match &*s.to_lowercase() {
|
|
"fg" => Ok(SpecType::Fg),
|
|
"bg" => Ok(SpecType::Bg),
|
|
"style" => Ok(SpecType::Style),
|
|
"none" => Ok(SpecType::None),
|
|
_ => Err(ColorError::UnrecognizedSpecType(s.to_string())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for Style {
|
|
type Err = ColorError;
|
|
|
|
fn from_str(s: &str) -> Result<Style, ColorError> {
|
|
match &*s.to_lowercase() {
|
|
"bold" => Ok(Style::Bold),
|
|
"nobold" => Ok(Style::NoBold),
|
|
"intense" => Ok(Style::Intense),
|
|
"nointense" => Ok(Style::NoIntense),
|
|
"underline" => Ok(Style::Underline),
|
|
"nounderline" => Ok(Style::NoUnderline),
|
|
_ => Err(ColorError::UnrecognizedStyle(s.to_string())),
|
|
}
|
|
}
|
|
}
|