--- a/rust/rhg/src/ui.rs Thu Feb 10 13:56:43 2022 +0100
+++ b/rust/rhg/src/ui.rs Thu Feb 10 12:59:32 2022 +0100
@@ -1,4 +1,7 @@
+use crate::color::ColorConfig;
+use crate::color::Effect;
use format_bytes::format_bytes;
+use format_bytes::write_bytes;
use hg::config::Config;
use hg::errors::HgError;
use hg::utils::files::get_bytes_from_os_string;
@@ -7,10 +10,10 @@
use std::io;
use std::io::{ErrorKind, Write};
-#[derive(Debug)]
pub struct Ui {
stdout: std::io::Stdout,
stderr: std::io::Stderr,
+ colors: Option<ColorConfig>,
}
/// The kind of user interface error
@@ -23,20 +26,26 @@
/// The commandline user interface
impl Ui {
- pub fn new(_config: &Config) -> Result<Self, HgError> {
+ pub fn new(config: &Config) -> Result<Self, HgError> {
Ok(Ui {
+ // If using something else, also adapt `isatty()` below.
stdout: std::io::stdout(),
+
stderr: std::io::stderr(),
+ colors: ColorConfig::new(config)?,
})
}
/// Default to no color if color configuration errors.
///
/// Useful when we’re already handling another error.
- pub fn new_infallible(_config: &Config) -> Self {
+ pub fn new_infallible(config: &Config) -> Self {
Ui {
+ // If using something else, also adapt `isatty()` below.
stdout: std::io::stdout(),
+
stderr: std::io::stderr(),
+ colors: ColorConfig::new(config).unwrap_or(None),
}
}
@@ -48,6 +57,11 @@
/// Write bytes to stdout
pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
+ // Hack to silence "unused" warnings
+ if false {
+ return self.write_stdout_labelled(bytes, "");
+ }
+
let mut stdout = self.stdout.lock();
stdout.write_all(bytes).or_else(handle_stdout_error)?;
@@ -64,6 +78,61 @@
stderr.flush().or_else(handle_stderr_error)
}
+ /// Write bytes to stdout with the given label
+ ///
+ /// Like the optional `label` parameter in `mercurial/ui.py`,
+ /// this label influences the color used for this output.
+ pub fn write_stdout_labelled(
+ &self,
+ bytes: &[u8],
+ label: &str,
+ ) -> Result<(), UiError> {
+ if let Some(colors) = &self.colors {
+ if let Some(effects) = colors.styles.get(label.as_bytes()) {
+ if !effects.is_empty() {
+ return self
+ .write_stdout_with_effects(bytes, effects)
+ .or_else(handle_stdout_error);
+ }
+ }
+ }
+ self.write_stdout(bytes)
+ }
+
+ fn write_stdout_with_effects(
+ &self,
+ bytes: &[u8],
+ effects: &[Effect],
+ ) -> io::Result<()> {
+ let stdout = &mut self.stdout.lock();
+ let mut write_line = |line: &[u8], first: bool| {
+ // `line` does not include the newline delimiter
+ if !first {
+ stdout.write_all(b"\n")?;
+ }
+ if line.is_empty() {
+ return Ok(());
+ }
+ /// 0x1B == 27 == 0o33
+ const ASCII_ESCAPE: &[u8] = b"\x1b";
+ write_bytes!(stdout, b"{}[0", ASCII_ESCAPE)?;
+ for effect in effects {
+ write_bytes!(stdout, b";{}", effect)?;
+ }
+ write_bytes!(stdout, b"m")?;
+ stdout.write_all(line)?;
+ write_bytes!(stdout, b"{}[0m", ASCII_ESCAPE)
+ };
+ let mut lines = bytes.split(|&byte| byte == b'\n');
+ if let Some(first) = lines.next() {
+ write_line(first, true)?;
+ for line in lines {
+ write_line(line, false)?
+ }
+ }
+ stdout.flush()
+ }
+
/// Return whether plain mode is active.
///
/// Plain mode means that all configuration variables which affect
@@ -83,7 +152,7 @@
}
}
-fn plain(opt_feature: Option<&str>) -> bool {
+pub fn plain(opt_feature: Option<&str>) -> bool {
if let Some(except) = env::var_os("HGPLAINEXCEPT") {
opt_feature.map_or(true, |feature| {
get_bytes_from_os_string(except)
@@ -154,3 +223,23 @@
let bytes = s.as_bytes();
Cow::Borrowed(bytes)
}
+
+/// Should formatted output be used?
+///
+/// Note: rhg does not have the formatter mechanism yet,
+/// but this is also used when deciding whether to use color.
+pub fn formatted(config: &Config) -> Result<bool, HgError> {
+ if let Some(formatted) = config.get_option(b"ui", b"formatted")? {
+ Ok(formatted)
+ } else {
+ isatty(config)
+ }
+}
+
+fn isatty(config: &Config) -> Result<bool, HgError> {
+ Ok(if config.get_bool(b"ui", b"nontty")? {
+ false
+ } else {
+ atty::is(atty::Stream::Stdout)
+ })
+}