##// END OF EJS Templates
rust-config: show default `null` is coerced incorrectly to `false`...
rust-config: show default `null` is coerced incorrectly to `false` Probably being too trigger happy about boolean values, I incorrectly set the transform for a `None` to a `Some(false)`. It would cause for example the `ui.formatted` value to be set to `Some(false)`, which turns off the colors among other things, when `None` would trigger the automatic behavior. This is fixed in the next commit.

File last commit:

r51432:74e4dbb0 default
r51875:10e57e3f default
Show More
ui.rs
307 lines | 9.1 KiB | application/rls-services+xml | RustLexer
Simon Sapin
rhg: Add support for colored output...
r49584 use crate::color::ColorConfig;
use crate::color::Effect;
Raphaël Gomès
rust-ui: refactor ui code for printing narrow/sparse warnings...
r50876 use crate::error::CommandError;
Raphaël Gomès
rhg: use `format_bytes!` for error messages...
r46598 use format_bytes::format_bytes;
Simon Sapin
rhg: Add support for colored output...
r49584 use format_bytes::write_bytes;
Simon Sapin
rhg: Pass a &Config to Ui::new...
r49581 use hg::config::Config;
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 use hg::config::PlainInfo;
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 use hg::errors::HgError;
Raphaël Gomès
rust-ui: refactor ui code for printing narrow/sparse warnings...
r50876 use hg::repo::Repo;
use hg::sparse;
use hg::utils::files::get_bytes_from_path;
use hg::PatternFileWarning;
Antoine Cezar
rhg: add a `DebugData` `Command` to prepare the `rhg debugdata` subcommand...
r46099 use std::borrow::Cow;
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 use std::io;
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 use std::io::{ErrorKind, Write};
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 pub struct Ui {
stdout: std::io::Stdout,
stderr: std::io::Stderr,
Simon Sapin
rhg: Add support for colored output...
r49584 colors: Option<ColorConfig>,
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 }
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
/// The kind of user interface error
pub enum UiError {
/// The standard output stream cannot be written to
StdoutError(io::Error),
/// The standard error stream cannot be written to
StderrError(io::Error),
}
/// The commandline user interface
impl Ui {
Simon Sapin
rhg: Add support for colored output...
r49584 pub fn new(config: &Config) -> Result<Self, HgError> {
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 Ok(Ui {
Simon Sapin
rhg: Add support for colored output...
r49584 // If using something else, also adapt `isatty()` below.
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 stdout: std::io::stdout(),
Simon Sapin
rhg: Add support for colored output...
r49584
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 stderr: std::io::stderr(),
Simon Sapin
rhg: Add support for colored output...
r49584 colors: ColorConfig::new(config)?,
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 })
}
/// Default to no color if color configuration errors.
///
/// Useful when we’re already handling another error.
Simon Sapin
rhg: Add support for colored output...
r49584 pub fn new_infallible(config: &Config) -> Self {
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 Ui {
Simon Sapin
rhg: Add support for colored output...
r49584 // If using something else, also adapt `isatty()` below.
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 stdout: std::io::stdout(),
Simon Sapin
rhg: Add support for colored output...
r49584
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 stderr: std::io::stderr(),
Simon Sapin
rhg: Add support for colored output...
r49584 colors: ColorConfig::new(config).unwrap_or(None),
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 }
}
/// Returns a buffered handle on stdout for faster batch printing
/// operations.
pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
StdoutBuffer::new(self.stdout.lock())
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 }
/// Write bytes to stdout
pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 let mut stdout = self.stdout.lock();
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stdout.write_all(bytes).or_else(handle_stdout_error)?;
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stdout.flush().or_else(handle_stdout_error)
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 }
/// Write bytes to stderr
pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 let mut stderr = self.stderr.lock();
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stderr.write_all(bytes).or_else(handle_stderr_error)?;
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stderr.flush().or_else(handle_stderr_error)
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 }
Pulkit Goyal
rhg: add ui.plain() and check it before showing relative paths in status...
r48990
Simon Sapin
rhg: Add support for colored output...
r49584 /// 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()
}
Simon Sapin
rhg: Add support for HGPLAINEXPECT...
r49580 }
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 // TODO: pass the PlainInfo to call sites directly and
// delete this function
Simon Sapin
rhg: Add support for colored output...
r49584 pub fn plain(opt_feature: Option<&str>) -> bool {
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 let plain_info = PlainInfo::from_env();
match opt_feature {
None => plain_info.is_plain(),
Some(feature) => plain_info.is_feature_plain(feature),
Pulkit Goyal
rhg: add ui.plain() and check it before showing relative paths in status...
r48990 }
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 }
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921
/// A buffered stdout writer for faster batch printing operations.
pub struct StdoutBuffer<W: Write> {
buf: io::BufWriter<W>,
}
impl<W: Write> StdoutBuffer<W> {
pub fn new(writer: W) -> Self {
let buf = io::BufWriter::new(writer);
Self { buf }
}
/// Write bytes to stdout buffer
pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 self.buf.write_all(bytes).or_else(handle_stdout_error)
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 }
/// Flush bytes to stdout
pub fn flush(&mut self) -> Result<(), UiError> {
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 self.buf.flush().or_else(handle_stdout_error)
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 }
}
Antoine Cezar
rhg: extract function handle_stdout_error...
r45925
/// Sometimes writing to stdout is not possible, try writing to stderr to
/// signal that failure, otherwise just bail.
fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
if let ErrorKind::BrokenPipe = error.kind() {
// This makes `| head` work for example
return Ok(());
}
let mut stderr = io::stderr();
stderr
Raphaël Gomès
rhg: use `format_bytes!` for error messages...
r46598 .write_all(&format_bytes!(
b"abort: {}\n",
error.to_string().as_bytes()
))
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 .map_err(UiError::StderrError)?;
Antoine Cezar
rhg: extract function handle_stdout_error...
r45925
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stderr.flush().map_err(UiError::StderrError)?;
Antoine Cezar
rhg: extract function handle_stdout_error...
r45925
Err(UiError::StdoutError(error))
}
Antoine Cezar
rhg: handle broken pipe error for stderr...
r45926
/// Sometimes writing to stderr is not possible.
fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
// A broken pipe should not result in a error
// like with `| head` for example
if let ErrorKind::BrokenPipe = error.kind() {
return Ok(());
}
Err(UiError::StdoutError(error))
}
Antoine Cezar
rhg: add a `DebugData` `Command` to prepare the `rhg debugdata` subcommand...
r46099
/// Encode rust strings according to the user system.
pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
// TODO encode for the user's system //
let bytes = s.as_bytes();
Cow::Borrowed(bytes)
}
Simon Sapin
rhg: Add support for colored output...
r49584
Raphaël Gomès
rhg: signal when falling back in logs...
r49622 /// Decode user system bytes to Rust string.
pub fn local_to_utf8(s: &[u8]) -> Cow<str> {
// TODO decode from the user's system
String::from_utf8_lossy(s)
}
Simon Sapin
rhg: Add support for colored output...
r49584 /// 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)
}
}
Arseniy Alekseyev
rhg: make `rhg files` work if `ui.relative-files=true` is specified
r51432 pub enum RelativePaths {
Legacy,
Bool(bool),
}
pub fn relative_paths(config: &Config) -> Result<RelativePaths, HgError> {
Ok(match config.get(b"ui", b"relative-paths") {
None | Some(b"legacy") => RelativePaths::Legacy,
_ => RelativePaths::Bool(config.get_bool(b"ui", b"relative-paths")?),
})
}
Simon Sapin
rhg: Add support for colored output...
r49584 fn isatty(config: &Config) -> Result<bool, HgError> {
Ok(if config.get_bool(b"ui", b"nontty")? {
false
} else {
atty::is(atty::Stream::Stdout)
})
}
Raphaël Gomès
rust-ui: refactor ui code for printing narrow/sparse warnings...
r50876
/// Return the formatted bytestring corresponding to a pattern file warning,
/// as expected by the CLI.
pub(crate) fn format_pattern_file_warning(
warning: &PatternFileWarning,
repo: &Repo,
) -> Vec<u8> {
match warning {
PatternFileWarning::InvalidSyntax(path, syntax) => format_bytes!(
b"{}: ignoring invalid syntax '{}'\n",
get_bytes_from_path(path),
&*syntax
),
PatternFileWarning::NoSuchFile(path) => {
let path = if let Ok(relative) =
path.strip_prefix(repo.working_directory_path())
{
relative
} else {
&*path
};
format_bytes!(
b"skipping unreadable pattern file '{}': \
No such file or directory\n",
get_bytes_from_path(path),
)
}
}
}
/// Print with `Ui` the formatted bytestring corresponding to a
/// sparse/narrow warning, as expected by the CLI.
pub(crate) fn print_narrow_sparse_warnings(
narrow_warnings: &[sparse::SparseWarning],
sparse_warnings: &[sparse::SparseWarning],
ui: &Ui,
repo: &Repo,
) -> Result<(), CommandError> {
for warning in narrow_warnings.iter().chain(sparse_warnings) {
match &warning {
sparse::SparseWarning::RootWarning { context, line } => {
let msg = format_bytes!(
b"warning: {} profile cannot use paths \"
starting with /, ignoring {}\n",
context,
line
);
ui.write_stderr(&msg)?;
}
sparse::SparseWarning::ProfileNotFound { profile, rev } => {
let msg = format_bytes!(
b"warning: sparse profile '{}' not found \"
in rev {} - ignoring it\n",
profile,
rev
);
ui.write_stderr(&msg)?;
}
sparse::SparseWarning::Pattern(e) => {
ui.write_stderr(&format_pattern_file_warning(e, repo))?;
}
}
}
Ok(())
}