2022-11-26 23:57:50 +00:00
|
|
|
|
// Copyright 2020 The Jujutsu Authors
|
2020-12-12 08:00:42 +00:00
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
2023-01-12 06:08:57 +00:00
|
|
|
|
use std::borrow::BorrowMut;
|
2020-12-12 08:00:42 +00:00
|
|
|
|
use std::collections::HashMap;
|
2023-01-12 06:06:43 +00:00
|
|
|
|
use std::io::{Error, Write};
|
2022-10-07 03:52:01 +00:00
|
|
|
|
use std::sync::Arc;
|
2023-01-12 06:08:57 +00:00
|
|
|
|
use std::{fmt, io};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
use crossterm::queue;
|
|
|
|
|
use crossterm::style::{Attribute, Color, SetAttribute, SetForegroundColor};
|
2023-01-03 08:07:03 +00:00
|
|
|
|
use itertools::Itertools;
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
|
// Lets the caller label strings and translates the labels to colors
|
2021-06-02 22:50:08 +00:00
|
|
|
|
pub trait Formatter: Write {
|
2021-04-07 06:05:16 +00:00
|
|
|
|
fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> {
|
|
|
|
|
self.write_all(data)
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-07 06:05:16 +00:00
|
|
|
|
fn write_str(&mut self, text: &str) -> io::Result<()> {
|
|
|
|
|
self.write_all(text.as_bytes())
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-10-06 11:16:41 +00:00
|
|
|
|
fn add_label(&mut self, label: &str) -> io::Result<()>;
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
2021-04-07 06:05:16 +00:00
|
|
|
|
fn remove_label(&mut self) -> io::Result<()>;
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
formatter: add a `with_label()` helper
There's a risk of forgetting to call `remove_label()` and I've wanted
to reduce that risk for a long time. I considered creating RAII
adapters that implement `Drop`, but I didn't like that that would
ignore errors (such as `BrokenPipe`) that can happen while emitting an
escape sequence in `remove_label()`. I would ideally have liked
Python's context managers here, but Rust doesn't have that. Instead,
we get to use closures. That works pretty well, except that we can't
return other errors than `io::Error` inside the closures. Even with
that limitation, we can use the new `with_label()` method in all but a
few cases.
We can't define the `with_label()` method directly in the trait
because `&mut self` is not a trait object, so we can't pass it on to
the closure (which expects a trait object). The solution is to use
`impl dyn Formatter` (thanks to @kupiakos for figuring that
out!). That unfortunately means that we can *only* call the function
on trait objects, so if `f` is a concrete formatter type
(e.g. `PlainTextFormatter`), then `f.with_label()` won't
compile. Since we only ever access the formatters as trait objects,
that's not really a problem, however.
2022-10-12 18:53:37 +00:00
|
|
|
|
impl dyn Formatter + '_ {
|
2023-01-12 06:46:41 +00:00
|
|
|
|
pub fn labeled<S: AsRef<str>>(&mut self, label: S) -> LabeledWriter<&mut Self, S> {
|
|
|
|
|
LabeledWriter {
|
|
|
|
|
formatter: self,
|
|
|
|
|
label,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
formatter: add a `with_label()` helper
There's a risk of forgetting to call `remove_label()` and I've wanted
to reduce that risk for a long time. I considered creating RAII
adapters that implement `Drop`, but I didn't like that that would
ignore errors (such as `BrokenPipe`) that can happen while emitting an
escape sequence in `remove_label()`. I would ideally have liked
Python's context managers here, but Rust doesn't have that. Instead,
we get to use closures. That works pretty well, except that we can't
return other errors than `io::Error` inside the closures. Even with
that limitation, we can use the new `with_label()` method in all but a
few cases.
We can't define the `with_label()` method directly in the trait
because `&mut self` is not a trait object, so we can't pass it on to
the closure (which expects a trait object). The solution is to use
`impl dyn Formatter` (thanks to @kupiakos for figuring that
out!). That unfortunately means that we can *only* call the function
on trait objects, so if `f` is a concrete formatter type
(e.g. `PlainTextFormatter`), then `f.with_label()` won't
compile. Since we only ever access the formatters as trait objects,
that's not really a problem, however.
2022-10-12 18:53:37 +00:00
|
|
|
|
pub fn with_label(
|
|
|
|
|
&mut self,
|
|
|
|
|
label: &str,
|
|
|
|
|
write_inner: impl FnOnce(&mut dyn Formatter) -> io::Result<()>,
|
|
|
|
|
) -> io::Result<()> {
|
|
|
|
|
self.add_label(label)?;
|
|
|
|
|
// Call `remove_label()` whether or not `write_inner()` fails, but don't let
|
|
|
|
|
// its error replace the one from `write_inner()`.
|
|
|
|
|
write_inner(self).and(self.remove_label())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 06:08:57 +00:00
|
|
|
|
/// `Formatter` wrapper to write a labeled message with `write!()` or
|
|
|
|
|
/// `writeln!()`.
|
|
|
|
|
pub struct LabeledWriter<T, S> {
|
|
|
|
|
formatter: T,
|
|
|
|
|
label: S,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T, S> LabeledWriter<T, S> {
|
|
|
|
|
pub fn new(formatter: T, label: S) -> Self {
|
|
|
|
|
LabeledWriter { formatter, label }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, T, S> LabeledWriter<T, S>
|
|
|
|
|
where
|
|
|
|
|
T: BorrowMut<dyn Formatter + 'a>,
|
|
|
|
|
S: AsRef<str>,
|
|
|
|
|
{
|
|
|
|
|
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
|
|
|
|
|
self.formatter
|
|
|
|
|
.borrow_mut()
|
|
|
|
|
.with_label(self.label.as_ref(), |formatter| formatter.write_fmt(args))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 03:52:01 +00:00
|
|
|
|
/// Creates `Formatter` instances with preconfigured parameters.
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct FormatterFactory {
|
|
|
|
|
kind: FormatterFactoryKind,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
enum FormatterFactoryKind {
|
|
|
|
|
PlainText,
|
|
|
|
|
Color {
|
2023-01-03 08:07:03 +00:00
|
|
|
|
rules: Arc<HashMap<Vec<String>, Style>>,
|
2022-10-07 03:52:01 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FormatterFactory {
|
2023-01-04 12:58:24 +00:00
|
|
|
|
pub fn prepare(config: &config::Config, color: bool) -> Self {
|
2022-10-07 03:52:01 +00:00
|
|
|
|
let kind = if color {
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let rules = Arc::new(rules_from_config(config));
|
|
|
|
|
FormatterFactoryKind::Color { rules }
|
2022-10-07 03:52:01 +00:00
|
|
|
|
} else {
|
|
|
|
|
FormatterFactoryKind::PlainText
|
|
|
|
|
};
|
|
|
|
|
FormatterFactory { kind }
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 11:37:51 +00:00
|
|
|
|
pub fn new_formatter<'output, W: Write + 'output>(
|
2022-10-07 03:52:01 +00:00
|
|
|
|
&self,
|
2022-10-07 11:37:51 +00:00
|
|
|
|
output: W,
|
2022-10-07 03:52:01 +00:00
|
|
|
|
) -> Box<dyn Formatter + 'output> {
|
|
|
|
|
match &self.kind {
|
|
|
|
|
FormatterFactoryKind::PlainText => Box::new(PlainTextFormatter::new(output)),
|
2023-01-03 08:07:03 +00:00
|
|
|
|
FormatterFactoryKind::Color { rules } => {
|
|
|
|
|
Box::new(ColorFormatter::new(output, rules.clone()))
|
2022-10-07 03:52:01 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 11:37:51 +00:00
|
|
|
|
pub struct PlainTextFormatter<W> {
|
|
|
|
|
output: W,
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 11:37:51 +00:00
|
|
|
|
impl<W> PlainTextFormatter<W> {
|
|
|
|
|
pub fn new(output: W) -> PlainTextFormatter<W> {
|
2020-12-12 08:00:42 +00:00
|
|
|
|
Self { output }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 11:37:51 +00:00
|
|
|
|
impl<W: Write> Write for PlainTextFormatter<W> {
|
2020-12-12 08:00:42 +00:00
|
|
|
|
fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
|
|
|
|
|
self.output.write(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn flush(&mut self) -> Result<(), Error> {
|
|
|
|
|
self.output.flush()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 11:37:51 +00:00
|
|
|
|
impl<W: Write> Formatter for PlainTextFormatter<W> {
|
2022-10-06 11:16:41 +00:00
|
|
|
|
fn add_label(&mut self, _label: &str) -> io::Result<()> {
|
2021-04-07 06:05:16 +00:00
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
2021-04-07 06:05:16 +00:00
|
|
|
|
fn remove_label(&mut self) -> io::Result<()> {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-07 16:02:47 +00:00
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
2023-01-03 08:07:03 +00:00
|
|
|
|
pub struct Style {
|
2023-01-07 16:02:47 +00:00
|
|
|
|
pub fg_color: Option<Color>,
|
2023-01-02 17:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-09 07:50:18 +00:00
|
|
|
|
impl Style {
|
|
|
|
|
fn merge(&mut self, other: &Style) {
|
|
|
|
|
self.fg_color = other.fg_color.or(self.fg_color);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 11:37:51 +00:00
|
|
|
|
pub struct ColorFormatter<W> {
|
|
|
|
|
output: W,
|
2023-01-03 08:07:03 +00:00
|
|
|
|
rules: Arc<HashMap<Vec<String>, Style>>,
|
2020-12-12 08:00:42 +00:00
|
|
|
|
labels: Vec<String>,
|
2023-01-02 17:36:04 +00:00
|
|
|
|
cached_styles: HashMap<Vec<String>, Style>,
|
|
|
|
|
current_style: Style,
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-07 17:36:19 +00:00
|
|
|
|
impl<W: Write> ColorFormatter<W> {
|
2023-01-03 08:07:03 +00:00
|
|
|
|
pub fn new(output: W, rules: Arc<HashMap<Vec<String>, Style>>) -> ColorFormatter<W> {
|
2021-06-02 22:50:08 +00:00
|
|
|
|
ColorFormatter {
|
2020-12-12 08:00:42 +00:00
|
|
|
|
output,
|
2023-01-03 08:07:03 +00:00
|
|
|
|
rules,
|
2020-12-12 08:00:42 +00:00
|
|
|
|
labels: vec![],
|
2023-01-02 17:36:04 +00:00
|
|
|
|
cached_styles: HashMap::new(),
|
|
|
|
|
current_style: Style::default(),
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 08:07:03 +00:00
|
|
|
|
pub fn for_config(output: W, config: &config::Config) -> ColorFormatter<W> {
|
|
|
|
|
let rules = rules_from_config(config);
|
|
|
|
|
Self::new(output, Arc::new(rules))
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-02 17:36:04 +00:00
|
|
|
|
fn current_style(&mut self) -> Style {
|
|
|
|
|
if let Some(cached) = self.cached_styles.get(&self.labels) {
|
|
|
|
|
cached.clone()
|
2020-12-12 08:00:42 +00:00
|
|
|
|
} else {
|
2023-01-09 07:50:18 +00:00
|
|
|
|
// We use the reverse list of matched indices as a measure of how well the rule
|
|
|
|
|
// matches the actual labels. For example, for rule "a d" and the actual labels
|
|
|
|
|
// "a b c d", we'll get [3,0]. We compare them by Rust's default Vec comparison.
|
|
|
|
|
// That means "a d" will trump both rule "d" (priority [3]) and rule
|
|
|
|
|
// "a b c" (priority [2,1,0]).
|
|
|
|
|
let mut matched_styles = vec![];
|
2023-01-03 08:07:03 +00:00
|
|
|
|
for (labels, style) in self.rules.as_ref() {
|
2023-01-09 07:50:18 +00:00
|
|
|
|
let mut labels_iter = self.labels.iter().enumerate();
|
|
|
|
|
// The indexes in the current label stack that match the required label.
|
|
|
|
|
let mut matched_indices = vec![];
|
2023-01-03 08:07:03 +00:00
|
|
|
|
for required_label in labels {
|
2023-01-09 07:50:18 +00:00
|
|
|
|
for (label_index, label) in &mut labels_iter {
|
|
|
|
|
if label == required_label {
|
|
|
|
|
matched_indices.push(label_index);
|
|
|
|
|
break;
|
2023-01-01 18:00:06 +00:00
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-09 07:50:18 +00:00
|
|
|
|
if matched_indices.len() == labels.len() {
|
|
|
|
|
matched_indices.reverse();
|
|
|
|
|
matched_styles.push((style, matched_indices));
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-09 07:50:18 +00:00
|
|
|
|
matched_styles.sort_by_key(|(_, indices)| indices.clone());
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
2023-01-09 07:50:18 +00:00
|
|
|
|
let mut style = Style::default();
|
|
|
|
|
for (matched_style, _) in matched_styles {
|
|
|
|
|
style.merge(matched_style);
|
|
|
|
|
}
|
2023-01-02 17:36:04 +00:00
|
|
|
|
self.cached_styles
|
|
|
|
|
.insert(self.labels.clone(), style.clone());
|
|
|
|
|
style
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-07 17:36:19 +00:00
|
|
|
|
|
2023-01-02 17:36:04 +00:00
|
|
|
|
fn write_new_style(&mut self) -> io::Result<()> {
|
|
|
|
|
let new_style = self.current_style();
|
|
|
|
|
if new_style != self.current_style {
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
// For now, make bright colors imply bold font. That better matches our
|
|
|
|
|
// behavior from when we used ANSI codes 30-37 plus an optional 1 for
|
|
|
|
|
// bold/bright (we now use code 38 for setting foreground color).
|
|
|
|
|
// TODO: Make boldness configurable separately from color
|
2023-01-02 17:36:04 +00:00
|
|
|
|
if !is_bright(&self.current_style.fg_color) && is_bright(&new_style.fg_color) {
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
queue!(self.output, SetAttribute(Attribute::Bold))?;
|
2023-01-02 17:36:04 +00:00
|
|
|
|
} else if !is_bright(&new_style.fg_color) && is_bright(&self.current_style.fg_color) {
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
queue!(self.output, SetAttribute(Attribute::Reset))?;
|
|
|
|
|
}
|
2023-01-07 16:02:47 +00:00
|
|
|
|
queue!(
|
|
|
|
|
self.output,
|
|
|
|
|
SetForegroundColor(new_style.fg_color.unwrap_or(Color::Reset))
|
|
|
|
|
)?;
|
2023-01-02 17:36:04 +00:00
|
|
|
|
self.current_style = new_style;
|
2023-01-07 17:36:19 +00:00
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-12-31 22:18:14 +00:00
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
2023-01-07 16:02:47 +00:00
|
|
|
|
fn is_bright(color: &Option<Color>) -> bool {
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
matches!(
|
2023-01-07 16:02:47 +00:00
|
|
|
|
color.unwrap_or(Color::Reset),
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
Color::DarkGrey
|
|
|
|
|
| Color::Red
|
|
|
|
|
| Color::Green
|
|
|
|
|
| Color::Yellow
|
|
|
|
|
| Color::Blue
|
|
|
|
|
| Color::Magenta
|
|
|
|
|
| Color::Cyan
|
|
|
|
|
| Color::White
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 08:07:03 +00:00
|
|
|
|
fn rules_from_config(config: &config::Config) -> HashMap<Vec<String>, Style> {
|
|
|
|
|
let mut result = HashMap::new();
|
|
|
|
|
if let Ok(table) = config.get_table("colors") {
|
|
|
|
|
for (key, value) in table {
|
|
|
|
|
let labels = key
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.map(ToString::to_string)
|
|
|
|
|
.collect_vec();
|
2023-01-06 05:58:52 +00:00
|
|
|
|
match value.kind {
|
|
|
|
|
config::ValueKind::String(color_name) => {
|
|
|
|
|
let style = Style {
|
|
|
|
|
fg_color: color_for_name(&color_name),
|
|
|
|
|
};
|
|
|
|
|
result.insert(labels, style);
|
|
|
|
|
}
|
|
|
|
|
config::ValueKind::Table(style_table) => {
|
|
|
|
|
let mut style = Style::default();
|
|
|
|
|
if let Some(value) = style_table.get("fg") {
|
|
|
|
|
if let config::ValueKind::String(color_name) = &value.kind {
|
|
|
|
|
style.fg_color = color_for_name(color_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result.insert(labels, style);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
2023-01-03 08:07:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-07 16:02:47 +00:00
|
|
|
|
fn color_for_name(color_name: &str) -> Option<Color> {
|
2022-12-31 22:18:14 +00:00
|
|
|
|
match color_name {
|
2023-01-07 16:02:47 +00:00
|
|
|
|
"black" => Some(Color::Black),
|
|
|
|
|
"red" => Some(Color::DarkRed),
|
|
|
|
|
"green" => Some(Color::DarkGreen),
|
|
|
|
|
"yellow" => Some(Color::DarkYellow),
|
|
|
|
|
"blue" => Some(Color::DarkBlue),
|
|
|
|
|
"magenta" => Some(Color::DarkMagenta),
|
|
|
|
|
"cyan" => Some(Color::DarkCyan),
|
|
|
|
|
"white" => Some(Color::Grey),
|
|
|
|
|
"bright black" => Some(Color::DarkGrey),
|
|
|
|
|
"bright red" => Some(Color::Red),
|
|
|
|
|
"bright green" => Some(Color::Green),
|
|
|
|
|
"bright yellow" => Some(Color::Yellow),
|
|
|
|
|
"bright blue" => Some(Color::Blue),
|
|
|
|
|
"bright magenta" => Some(Color::Magenta),
|
|
|
|
|
"bright cyan" => Some(Color::Cyan),
|
|
|
|
|
"bright white" => Some(Color::White),
|
|
|
|
|
_ => None,
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 11:37:51 +00:00
|
|
|
|
impl<W: Write> Write for ColorFormatter<W> {
|
2020-12-12 08:00:42 +00:00
|
|
|
|
fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
|
|
|
|
|
self.output.write(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn flush(&mut self) -> Result<(), Error> {
|
|
|
|
|
self.output.flush()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 11:37:51 +00:00
|
|
|
|
impl<W: Write> Formatter for ColorFormatter<W> {
|
2022-10-06 11:16:41 +00:00
|
|
|
|
fn add_label(&mut self, label: &str) -> io::Result<()> {
|
|
|
|
|
self.labels.push(label.to_owned());
|
2023-01-02 17:36:04 +00:00
|
|
|
|
self.write_new_style()
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-07 06:05:16 +00:00
|
|
|
|
fn remove_label(&mut self) -> io::Result<()> {
|
2020-12-12 08:00:42 +00:00
|
|
|
|
self.labels.pop();
|
2023-01-02 17:36:04 +00:00
|
|
|
|
self.write_new_style()
|
2020-12-12 08:00:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-23 04:15:37 +00:00
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2023-01-03 08:07:03 +00:00
|
|
|
|
fn config_from_string(text: &str) -> config::Config {
|
|
|
|
|
config::Config::builder()
|
|
|
|
|
.add_source(config::File::from_str(text, config::FileFormat::Toml))
|
|
|
|
|
.build()
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-23 04:15:37 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_plaintext_formatter() {
|
|
|
|
|
// Test that PlainTextFormatter ignores labels.
|
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = PlainTextFormatter::new(&mut output);
|
|
|
|
|
formatter.add_label("warning").unwrap();
|
|
|
|
|
formatter.write_str("hello").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"hello");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_color_codes() {
|
|
|
|
|
// Test the color code for each color.
|
|
|
|
|
let colors = [
|
|
|
|
|
"black",
|
|
|
|
|
"red",
|
|
|
|
|
"green",
|
|
|
|
|
"yellow",
|
|
|
|
|
"blue",
|
|
|
|
|
"magenta",
|
|
|
|
|
"cyan",
|
|
|
|
|
"white",
|
|
|
|
|
"bright black",
|
|
|
|
|
"bright red",
|
|
|
|
|
"bright green",
|
|
|
|
|
"bright yellow",
|
|
|
|
|
"bright blue",
|
|
|
|
|
"bright magenta",
|
|
|
|
|
"bright cyan",
|
|
|
|
|
"bright white",
|
|
|
|
|
];
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut config_builder = config::Config::builder();
|
|
|
|
|
for color in colors {
|
2022-12-23 04:15:37 +00:00
|
|
|
|
// Use the color name as the label.
|
2023-01-03 08:07:03 +00:00
|
|
|
|
config_builder = config_builder
|
|
|
|
|
.set_override(format!("colors.{}", color.replace(' ', "-")), color)
|
|
|
|
|
.unwrap();
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
let mut output: Vec<u8> = vec![];
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut formatter =
|
|
|
|
|
ColorFormatter::for_config(&mut output, &config_builder.build().unwrap());
|
2022-12-23 04:15:37 +00:00
|
|
|
|
for color in colors {
|
|
|
|
|
formatter.add_label(&color.replace(' ', "-")).unwrap();
|
|
|
|
|
formatter.write_str(&format!(" {color} ")).unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str("\n").unwrap();
|
|
|
|
|
}
|
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @r###"
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
[38;5;0m black [39m
|
|
|
|
|
[38;5;1m red [39m
|
|
|
|
|
[38;5;2m green [39m
|
|
|
|
|
[38;5;3m yellow [39m
|
|
|
|
|
[38;5;4m blue [39m
|
|
|
|
|
[38;5;5m magenta [39m
|
|
|
|
|
[38;5;6m cyan [39m
|
|
|
|
|
[38;5;7m white [39m
|
|
|
|
|
[1m[38;5;8m bright black [0m[39m
|
|
|
|
|
[1m[38;5;9m bright red [0m[39m
|
|
|
|
|
[1m[38;5;10m bright green [0m[39m
|
|
|
|
|
[1m[38;5;11m bright yellow [0m[39m
|
|
|
|
|
[1m[38;5;12m bright blue [0m[39m
|
|
|
|
|
[1m[38;5;13m bright magenta [0m[39m
|
|
|
|
|
[1m[38;5;14m bright cyan [0m[39m
|
|
|
|
|
[1m[38;5;15m bright white [0m[39m
|
2022-12-23 04:15:37 +00:00
|
|
|
|
"###);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_single_label() {
|
|
|
|
|
// Test that a single label can be colored and that the color is reset
|
|
|
|
|
// afterwards.
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors.inside = "green"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.write_str(" before ").unwrap();
|
|
|
|
|
formatter.add_label("inside").unwrap();
|
|
|
|
|
formatter.write_str(" inside ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str(" after ").unwrap();
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" before [38;5;2m inside [39m after ");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-06 05:58:52 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_attributes() {
|
|
|
|
|
// Test that each attribute of the style can be set.
|
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors.red_fg = { fg = "red" }
|
|
|
|
|
"#,
|
|
|
|
|
);
|
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
|
|
|
|
formatter.add_label("red_fg").unwrap();
|
|
|
|
|
formatter.write_str(" fg only ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"[38;5;1m fg only [39m");
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-23 04:15:37 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_no_space() {
|
|
|
|
|
// Test that two different colors can touch.
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors.red = "red"
|
|
|
|
|
colors.green = "green"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.write_str("before").unwrap();
|
|
|
|
|
formatter.add_label("red").unwrap();
|
|
|
|
|
formatter.write_str("first").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.add_label("green").unwrap();
|
|
|
|
|
formatter.write_str("second").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str("after").unwrap();
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"before[38;5;1mfirst[39m[38;5;2msecond[39mafter");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_ansi_codes_in_text() {
|
|
|
|
|
// Test that ANSI codes in the input text are escaped.
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors.red = "red"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.add_label("red").unwrap();
|
|
|
|
|
formatter
|
|
|
|
|
.write_str("\x1b[1mnot actually bold\x1b[0m")
|
|
|
|
|
.unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
// TODO: Replace the ANSI escape (\x1b) by something else (🌈?)
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"[38;5;1m[1mnot actually bold[0m[39m");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_nested() {
|
|
|
|
|
// A color can be associated with a combination of labels. A more specific match
|
|
|
|
|
// overrides a less specific match. After the inner label is removed, the outer
|
|
|
|
|
// color is used again (we don't reset).
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors.outer = "blue"
|
|
|
|
|
colors.inner = "red"
|
|
|
|
|
colors."outer inner" = "green"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.write_str(" before outer ").unwrap();
|
|
|
|
|
formatter.add_label("outer").unwrap();
|
|
|
|
|
formatter.write_str(" before inner ").unwrap();
|
|
|
|
|
formatter.add_label("inner").unwrap();
|
|
|
|
|
formatter.write_str(" inside inner ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str(" after inner ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str(" after outer ").unwrap();
|
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(),
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
@" before outer [38;5;4m before inner [38;5;2m inside inner [38;5;4m after inner [39m after outer ");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_partial_match() {
|
|
|
|
|
// A partial match doesn't count
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors."outer inner" = "green"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.add_label("outer").unwrap();
|
|
|
|
|
formatter.write_str(" not colored ").unwrap();
|
|
|
|
|
formatter.add_label("inner").unwrap();
|
|
|
|
|
formatter.write_str(" colored ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str(" not colored ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(),
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
@" not colored [38;5;2m colored [39m not colored ");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_unrecognized_color() {
|
|
|
|
|
// An unrecognized color is ignored; it doesn't reset the color.
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors."outer" = "red"
|
|
|
|
|
colors."outer inner" = "bloo"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.add_label("outer").unwrap();
|
|
|
|
|
formatter.write_str(" red before ").unwrap();
|
|
|
|
|
formatter.add_label("inner").unwrap();
|
|
|
|
|
formatter.write_str(" still red inside ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str(" also red afterwards ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(),
|
2023-01-09 07:50:18 +00:00
|
|
|
|
@"[38;5;1m red before still red inside also red afterwards [39m");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_sibling() {
|
|
|
|
|
// A partial match on one rule does not eliminate other rules.
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors."outer1 inner1" = "red"
|
|
|
|
|
colors.inner2 = "green"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.add_label("outer1").unwrap();
|
|
|
|
|
formatter.add_label("inner2").unwrap();
|
|
|
|
|
formatter.write_str(" hello ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(),
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
@"[38;5;2m hello [39m");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_reverse_order() {
|
|
|
|
|
// Rules don't match labels out of order
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors."inner outer" = "green"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.add_label("outer").unwrap();
|
|
|
|
|
formatter.add_label("inner").unwrap();
|
|
|
|
|
formatter.write_str(" hello ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
formatter: use `crossterm` for colors
Let's use `crossterm` to make `ColorFormatter` a little more readable,
and maybe also more portable.
This uses the `SetForegroundColor()` function, which uses the escapes
for 256-color support (code 38) instead of the 8-color escapes (codes
30-37) combined with bold/bright (code 1) we were using before. IIUC,
most terminals support the 16 base colors when using the 256-color
escape even if they don't support all the 256 colors. It seems like an
improvement to use actual color codes for the bright colors too,
instead of assuming that terminals render bold as bright (even though
most terminals do).
Before this commit, we relied on ANSI escape 1 - which is specified to
make the font bold - to make the color brighter. That's why we call
the colors "bright blue" etc. When we switch from using code 30-37 to
using 38 to let our color config just control the color (not using
escape1), we therefore lose the bold font on many terminals (at least
in iTerm2 and in the terminal application on my Debian work
computer). As a workaround, I made us still use escape 1 when the
bright colors are used. I'll make boldness a separately configurable
attribute soon. Then we'll be able to remove this hack.
With the switch to `crossterm`, we also reset just the foreground
color (code 39) instead of resetting all attributes (code 0). That
also seems like an improvement, probably making it easier for us to
later support different background colors, underlining, etc.
2022-12-27 07:23:19 +00:00
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" hello ");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_color_formatter_innermost_wins() {
|
|
|
|
|
// When two labels match, the innermost one wins.
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let config = config_from_string(
|
|
|
|
|
r#"
|
|
|
|
|
colors."a" = "red"
|
|
|
|
|
colors."b" = "green"
|
|
|
|
|
colors."a c" = "blue"
|
|
|
|
|
colors."b c" = "yellow"
|
|
|
|
|
"#,
|
2022-12-23 04:15:37 +00:00
|
|
|
|
);
|
2023-01-03 08:07:03 +00:00
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
|
|
|
let mut formatter = ColorFormatter::for_config(&mut output, &config);
|
2022-12-23 04:15:37 +00:00
|
|
|
|
formatter.add_label("a").unwrap();
|
|
|
|
|
formatter.write_str(" a1 ").unwrap();
|
|
|
|
|
formatter.add_label("b").unwrap();
|
|
|
|
|
formatter.write_str(" b1 ").unwrap();
|
|
|
|
|
formatter.add_label("c").unwrap();
|
|
|
|
|
formatter.write_str(" c ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str(" b2 ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
|
|
|
|
formatter.write_str(" a2 ").unwrap();
|
|
|
|
|
formatter.remove_label().unwrap();
|
2023-01-09 07:50:18 +00:00
|
|
|
|
insta::assert_snapshot!(String::from_utf8(output).unwrap(),
|
|
|
|
|
@"[38;5;1m a1 [38;5;2m b1 [38;5;3m c [38;5;2m b2 [38;5;1m a2 [39m");
|
2022-12-23 04:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|