// Copyright 2020 The Jujutsu Authors//// 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. use std::env;use std::error;use std::fmt;use std::io;use std::io::IsTerminal as _;use std::io::PipeWriter;use std::io::Stderr;use std::io::StderrLock;use std::io::Stdout;use std::io::StdoutLock;use std::io::Write;use std::iter;use std::mem;use std::process::Child;use std::process::ChildStdin;use std::process::Stdio;use std::thread;use std::thread::JoinHandle; use itertools::Itertools as _;use jj_lib::config::ConfigGetError;use jj_lib::config::StackedConfig;use tracing::instrument; use crate::command_error::CommandError;use crate::config::CommandNameAndArgs;use crate::formatter::Formatter;use crate::formatter::FormatterExt as _;use crate::formatter::FormatterFactory;use crate::formatter::HeadingLabeledWriter;use crate::formatter::LabeledScope;use crate::formatter::PlainTextFormatter; const BUILTIN_PAGER_NAME: &str = ":builtin"; enum UiOutput { Terminal { stdout: Stdout, stderr: Stderr, }, Paged { child: Child, child_stdin: ChildStdin, }, BuiltinPaged { out_wr: PipeWriter, err_wr: PipeWriter, pager_thread: JoinHandle<streampager::Result<()>>, }, Null,} impl UiOutput { fn new_terminal() -> Self { Self::Terminal { stdout: io::stdout(), stderr: io::stderr(), } } fn new_paged(pager_cmd: &CommandNameAndArgs) -> io::Result<Self> { let mut cmd = pager_cmd.to_command(); tracing::info!(?cmd, "spawning pager"); let mut child = cmd.stdin(Stdio::piped()).spawn()?; let child_stdin = child.stdin.take().unwrap(); Ok(Self::Paged { child, child_stdin }) } fn new_builtin_paged(config: &StreampagerConfig) -> streampager::Result<Self> { let streampager_config = streampager::config::Config { wrapping_mode: config.wrapping.into(), interface_mode: config.streampager_interface_mode(), show_ruler: config.show_ruler, // We could make scroll-past-eof configurable, but I'm guessing people // will not miss it. If we do make it configurable, we should mention // that it's a bad idea to turn this on if `interface=quit-if-one-page`, // as it can leave a lot of empty lines on the screen after exiting. scroll_past_eof: false, ..Default::default() }; let mut pager = streampager::Pager::new_using_stdio_with_config(streampager_config)?; // Use native pipe, which can be attached to child process. The stdout // stream could be an in-process channel, but the cost of extra syscalls // wouldn't matter. let (out_rd, out_wr) = io::pipe()?; let (err_rd, err_wr) = io::pipe()?; pager.add_stream(out_rd, "")?; pager.add_error_stream(err_rd, "stderr")?; Ok(Self::BuiltinPaged { out_wr, err_wr, pager_thread: thread::spawn(|| pager.run()), }) } fn finalize(self, ui: &Ui) { match self { Self::Terminal { .. } => { /* no-op */ } Self::Paged { mut child, child_stdin, } => { drop(child_stdin); if let Err(err) = child.wait() { // It's possible (though unlikely) that this write fails, but // this function gets called so late that there's not much we // can do about it. writeln!( ui.warning_default(), "Failed to wait on pager: {err}", err = format_error_with_sources(&err), ) .ok(); } } Self::BuiltinPaged { out_wr, err_wr, pager_thread, } => { drop(out_wr); drop(err_wr); match pager_thread.join() { Ok(Ok(())) => {} Ok(Err(err)) => { writeln!( ui.warning_default(), "Failed to run builtin pager: {err}", err = format_error_with_sources(&err), ) .ok(); } Err(_) => { writeln!(ui.warning_default(), "Builtin pager crashed.").ok(); } } } Self::Null => {} } }} pub enum UiStdout<'a> { Terminal(StdoutLock<'static>), Paged(&'a ChildStdin), Builtin(&'a PipeWriter), Null(io::Sink),} pub enum UiStderr<'a> { Terminal(StderrLock<'static>), Paged(&'a ChildStdin), Builtin(&'a PipeWriter), Null(io::Sink),} macro_rules! for_outputs { ($ty:ident, $output:expr, $pat:pat => $expr:expr) => { match $output { $ty::Terminal($pat) => $expr, $ty::Paged($pat) => $expr, $ty::Builtin($pat) => $expr, $ty::Null($pat) => $expr, } };} impl Write for UiStdout<'_> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { for_outputs!(Self, self, w => w.write(buf)) } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { for_outputs!(Self, self, w => w.write_all(buf)) } fn flush(&mut self) -> io::Result<()> { for_outputs!(Self, self, w => w.flush()) }} impl Write for UiStderr<'_> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { for_outputs!(Self, self, w => w.write(buf)) } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { for_outputs!(Self, self, w => w.write_all(buf)) } fn flush(&mut self) -> io::Result<()> { for_outputs!(Self, self, w => w.flush()) }} pub struct Ui { quiet: bool, pager: PagerConfig, progress_indicator: bool, formatter_factory: FormatterFactory, output: UiOutput,} #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, clap::ValueEnum)]#[serde(rename_all = "kebab-case")]pub enum ColorChoice { Always, Never, Debug, Auto,} impl fmt::Display for ColorChoice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Always => "always", Self::Never => "never", Self::Debug => "debug", Self::Auto => "auto", }; write!(f, "{s}") }} fn prepare_formatter_factory( config: &StackedConfig, stdout: &Stdout,) -> Result<FormatterFactory, ConfigGetError> { let terminal = stdout.is_terminal(); let (color, debug) = match config.get("ui.color")? { ColorChoice::Always => (true, false), ColorChoice::Never => (false, false), ColorChoice::Debug => (true, true), ColorChoice::Auto => (terminal, false), }; if color { FormatterFactory::color(config, debug) } else if terminal { // Sanitize ANSI escape codes if we're printing to a terminal. Doesn't // affect ANSI escape codes that originate from the formatter itself. Ok(FormatterFactory::sanitized()) } else { Ok(FormatterFactory::plain_text()) }} #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)]#[serde(rename_all(deserialize = "kebab-case"))]pub enum PaginationChoice { Never, Auto,} #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)]#[serde(rename_all(deserialize = "kebab-case"))]pub enum StreampagerAlternateScreenMode { QuitIfOnePage, FullScreenClearOutput, QuitQuicklyOrClearOutput,} #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)]#[serde(rename_all(deserialize = "kebab-case"))]enum StreampagerWrappingMode { None, Word, Anywhere,} impl From<StreampagerWrappingMode> for streampager::config::WrappingMode { fn from(val: StreampagerWrappingMode) -> Self { match val { StreampagerWrappingMode::None => Self::Unwrapped, StreampagerWrappingMode::Word => Self::WordBoundary, StreampagerWrappingMode::Anywhere => Self::GraphemeBoundary, } }} #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)]#[serde(rename_all(deserialize = "kebab-case"))]struct StreampagerConfig { interface: StreampagerAlternateScreenMode, wrapping: StreampagerWrappingMode, show_ruler: bool, // TODO: Add an `quit-quickly-delay-seconds` floating point option or a // `quit-quickly-delay` option that takes a 's' or 'ms' suffix. Note that as // of this writing, floating point numbers do not work with `--config`} impl StreampagerConfig { fn streampager_interface_mode(&self) -> streampager::config::InterfaceMode { use StreampagerAlternateScreenMode::*; use streampager::config::InterfaceMode; match self.interface { // InterfaceMode::Direct not implemented FullScreenClearOutput => InterfaceMode::FullScreen, QuitIfOnePage => InterfaceMode::Hybrid, QuitQuicklyOrClearOutput => InterfaceMode::Delayed(std::time::Duration::from_secs(2)), } }} enum PagerConfig { Disabled, Builtin(StreampagerConfig), External(CommandNameAndArgs),} impl PagerConfig { fn from_config(config: &StackedConfig) -> Result<Self, ConfigGetError> { if matches!(config.get("ui.paginate")?, PaginationChoice::Never) { return Ok(Self::Disabled); }; let args: CommandNameAndArgs = config.get("ui.pager")?; if args.as_str() == Some(BUILTIN_PAGER_NAME) { Ok(Self::Builtin(config.get("ui.streampager")?)) } else { Ok(Self::External(args)) } }} impl Ui { pub fn null() -> Self { Self { quiet: true, pager: PagerConfig::Disabled, progress_indicator: false, formatter_factory: FormatterFactory::plain_text(), output: UiOutput::Null, } } pub fn with_config(config: &StackedConfig) -> Result<Self, CommandError> { let formatter_factory = prepare_formatter_factory(config, &io::stdout())?; Ok(Self { quiet: config.get("ui.quiet")?, formatter_factory, pager: PagerConfig::from_config(config)?, progress_indicator: config.get("ui.progress-indicator")?, output: UiOutput::new_terminal(), }) } pub fn reset(&mut self, config: &StackedConfig) -> Result<(), CommandError> { self.quiet = config.get("ui.quiet")?; self.pager = PagerConfig::from_config(config)?; self.progress_indicator = config.get("ui.progress-indicator")?; self.formatter_factory = prepare_formatter_factory(config, &io::stdout())?; Ok(()) } /// Switches the output to use the pager, if allowed. #[instrument(skip_all)] pub fn request_pager(&mut self) { if !matches!(&self.output, UiOutput::Terminal { stdout, .. } if stdout.is_terminal()) { return; } let new_output = match &self.pager { PagerConfig::Disabled => { return; } PagerConfig::Builtin(streampager_config) => { UiOutput::new_builtin_paged(streampager_config) .inspect_err(|err| { writeln!( self.warning_default(), "Failed to set up builtin pager: {err}", err = format_error_with_sources(err), ) .ok(); }) .ok() } PagerConfig::External(command_name_and_args) => { UiOutput::new_paged(command_name_and_args) .inspect_err(|err| { // The pager executable couldn't be found or couldn't be run writeln!( self.warning_default(), "Failed to spawn pager '{name}': {err}", name = command_name_and_args.split_name(), err = format_error_with_sources(err), ) .ok(); writeln!(self.hint_default(), "Consider using the `:builtin` pager.").ok(); }) .ok() } }; if let Some(output) = new_output { self.output = output; } } pub fn color(&self) -> bool { self.formatter_factory.is_color() } pub fn new_formatter<'output, W: Write + 'output>( &self, output: W, ) -> Box<dyn Formatter + 'output> { self.formatter_factory.new_formatter(output) } /// Locked stdout stream. pub fn stdout(&self) -> UiStdout<'_> { match &self.output { UiOutput::Terminal { stdout, .. } => UiStdout::Terminal(stdout.lock()), UiOutput::Paged { child_stdin, .. } => UiStdout::Paged(child_stdin), UiOutput::BuiltinPaged { out_wr, .. } => UiStdout::Builtin(out_wr), UiOutput::Null => UiStdout::Null(io::sink()), } } /// Creates a formatter for the locked stdout stream. /// /// Labels added to the returned formatter should be removed by caller. /// Otherwise the last color would persist. pub fn stdout_formatter(&self) -> Box<dyn Formatter + '_> { for_outputs!(UiStdout, self.stdout(), w => self.new_formatter(w)) } /// Locked stderr stream. pub fn stderr(&self) -> UiStderr<'_> { match &self.output { UiOutput::Terminal { stderr, .. } => UiStderr::Terminal(stderr.lock()), UiOutput::Paged { child_stdin, .. } => UiStderr::Paged(child_stdin), UiOutput::BuiltinPaged { err_wr, .. } => UiStderr::Builtin(err_wr), UiOutput::Null => UiStderr::Null(io::sink()), } } /// Creates a formatter for the locked stderr stream. pub fn stderr_formatter(&self) -> Box<dyn Formatter + '_> { for_outputs!(UiStderr, self.stderr(), w => self.new_formatter(w)) } /// Stderr stream to be attached to a child process. pub fn stderr_for_child(&self) -> io::Result<Stdio> { match &self.output { UiOutput::Terminal { .. } => Ok(Stdio::inherit()), UiOutput::Paged { child_stdin, .. } => Ok(duplicate_child_stdin(child_stdin)?.into()), UiOutput::BuiltinPaged { err_wr, .. } => Ok(err_wr.try_clone()?.into()), UiOutput::Null => Ok(Stdio::null()), } } /// Whether continuous feedback should be displayed for long-running /// operations pub fn use_progress_indicator(&self) -> bool { match &self.output { UiOutput::Terminal { stderr, .. } => self.progress_indicator && stderr.is_terminal(), UiOutput::Paged { .. } => false, UiOutput::BuiltinPaged { .. } => false, UiOutput::Null => false, } } pub fn progress_output(&self) -> Option<ProgressOutput<std::io::Stderr>> { self.use_progress_indicator() .then(ProgressOutput::for_stderr) } /// Writer to print an update that's not part of the command's main output. pub fn status(&self) -> Box<dyn Write + '_> { if self.quiet { Box::new(io::sink()) } else { Box::new(self.stderr()) } } /// A formatter to print an update that's not part of the command's main /// output. Returns `None` if `--quiet` was requested. pub fn status_formatter(&self) -> Option<Box<dyn Formatter + '_>> { (!self.quiet).then(|| self.stderr_formatter()) } /// Writer to print hint with the default "Hint: " heading. pub fn hint_default(&self) -> HeadingLabeledWriter<Box<dyn Formatter + '_>, &'static str> {Lines 1–500 of 775. Load more
// Copyright 2020 The Jujutsu Authors // // 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. use std::env; use std::error; use std::fmt; use std::io; use std::io::IsTerminal as _; use std::io::PipeWriter; use std::io::Stderr; use std::io::StderrLock; use std::io::Stdout; use std::io::StdoutLock; use std::io::Write; use std::iter; use std::mem; use std::process::Child; use std::process::ChildStdin; use std::process::Stdio; use std::thread; use std::thread::JoinHandle; use itertools::Itertools as _; use jj_lib::config::ConfigGetError; use jj_lib::config::StackedConfig; use tracing::instrument; use crate::command_error::CommandError; use crate::config::CommandNameAndArgs; use crate::formatter::Formatter; use crate::formatter::FormatterExt as _; use crate::formatter::FormatterFactory; use crate::formatter::HeadingLabeledWriter; use crate::formatter::LabeledScope; use crate::formatter::PlainTextFormatter; const BUILTIN_PAGER_NAME: &str = ":builtin"; enum UiOutput { Terminal { stdout: Stdout, stderr: Stderr, }, Paged { child: Child, child_stdin: ChildStdin, }, BuiltinPaged { out_wr: PipeWriter, err_wr: PipeWriter, pager_thread: JoinHandle<streampager::Result<()>>, }, Null, } impl UiOutput { fn new_terminal() -> Self { Self::Terminal { stdout: io::stdout(), stderr: io::stderr(), } } fn new_paged(pager_cmd: &CommandNameAndArgs) -> io::Result<Self> { let mut cmd = pager_cmd.to_command(); tracing::info!(?cmd, "spawning pager"); let mut child = cmd.stdin(Stdio::piped()).spawn()?; let child_stdin = child.stdin.take().unwrap(); Ok(Self::Paged { child, child_stdin }) } fn new_builtin_paged(config: &StreampagerConfig) -> streampager::Result<Self> { let streampager_config = streampager::config::Config { wrapping_mode: config.wrapping.into(), interface_mode: config.streampager_interface_mode(), show_ruler: config.show_ruler, // We could make scroll-past-eof configurable, but I'm guessing people // will not miss it. If we do make it configurable, we should mention // that it's a bad idea to turn this on if `interface=quit-if-one-page`, // as it can leave a lot of empty lines on the screen after exiting. scroll_past_eof: false, ..Default::default() }; let mut pager = streampager::Pager::new_using_stdio_with_config(streampager_config)?; // Use native pipe, which can be attached to child process. The stdout // stream could be an in-process channel, but the cost of extra syscalls // wouldn't matter. let (out_rd, out_wr) = io::pipe()?; let (err_rd, err_wr) = io::pipe()?; pager.add_stream(out_rd, "")?; pager.add_error_stream(err_rd, "stderr")?; Ok(Self::BuiltinPaged { out_wr, err_wr, pager_thread: thread::spawn(|| pager.run()), }) } fn finalize(self, ui: &Ui) { match self { Self::Terminal { .. } => { /* no-op */ } Self::Paged { mut child, child_stdin, } => { drop(child_stdin); if let Err(err) = child.wait() { // It's possible (though unlikely) that this write fails, but // this function gets called so late that there's not much we // can do about it. writeln!( ui.warning_default(), "Failed to wait on pager: {err}", err = format_error_with_sources(&err), ) .ok(); } } Self::BuiltinPaged { out_wr, err_wr, pager_thread, } => { drop(out_wr); drop(err_wr); match pager_thread.join() { Ok(Ok(())) => {} Ok(Err(err)) => { writeln!( ui.warning_default(), "Failed to run builtin pager: {err}", err = format_error_with_sources(&err), ) .ok(); } Err(_) => { writeln!(ui.warning_default(), "Builtin pager crashed.").ok(); } } } Self::Null => {} } } } pub enum UiStdout<'a> { Terminal(StdoutLock<'static>), Paged(&'a ChildStdin), Builtin(&'a PipeWriter), Null(io::Sink), } pub enum UiStderr<'a> { Terminal(StderrLock<'static>), Paged(&'a ChildStdin), Builtin(&'a PipeWriter), Null(io::Sink), } macro_rules! for_outputs { ($ty:ident, $output:expr, $pat:pat => $expr:expr) => { match $output { $ty::Terminal($pat) => $expr, $ty::Paged($pat) => $expr, $ty::Builtin($pat) => $expr, $ty::Null($pat) => $expr, } }; } impl Write for UiStdout<'_> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { for_outputs!(Self, self, w => w.write(buf)) } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { for_outputs!(Self, self, w => w.write_all(buf)) } fn flush(&mut self) -> io::Result<()> { for_outputs!(Self, self, w => w.flush()) } } impl Write for UiStderr<'_> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { for_outputs!(Self, self, w => w.write(buf)) } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { for_outputs!(Self, self, w => w.write_all(buf)) } fn flush(&mut self) -> io::Result<()> { for_outputs!(Self, self, w => w.flush()) } } pub struct Ui { quiet: bool, pager: PagerConfig, progress_indicator: bool, formatter_factory: FormatterFactory, output: UiOutput, } #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, clap::ValueEnum)] #[serde(rename_all = "kebab-case")] pub enum ColorChoice { Always, Never, Debug, Auto, } impl fmt::Display for ColorChoice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Always => "always", Self::Never => "never", Self::Debug => "debug", Self::Auto => "auto", }; write!(f, "{s}") } } fn prepare_formatter_factory( config: &StackedConfig, stdout: &Stdout, ) -> Result<FormatterFactory, ConfigGetError> { let terminal = stdout.is_terminal(); let (color, debug) = match config.get("ui.color")? { ColorChoice::Always => (true, false), ColorChoice::Never => (false, false), ColorChoice::Debug => (true, true), ColorChoice::Auto => (terminal, false), }; if color { FormatterFactory::color(config, debug) } else if terminal { // Sanitize ANSI escape codes if we're printing to a terminal. Doesn't // affect ANSI escape codes that originate from the formatter itself. Ok(FormatterFactory::sanitized()) } else { Ok(FormatterFactory::plain_text()) } } #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)] #[serde(rename_all(deserialize = "kebab-case"))] pub enum PaginationChoice { Never, Auto, } #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)] #[serde(rename_all(deserialize = "kebab-case"))] pub enum StreampagerAlternateScreenMode { QuitIfOnePage, FullScreenClearOutput, QuitQuicklyOrClearOutput, } #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)] #[serde(rename_all(deserialize = "kebab-case"))] enum StreampagerWrappingMode { None, Word, Anywhere, } impl From<StreampagerWrappingMode> for streampager::config::WrappingMode { fn from(val: StreampagerWrappingMode) -> Self { match val { StreampagerWrappingMode::None => Self::Unwrapped, StreampagerWrappingMode::Word => Self::WordBoundary, StreampagerWrappingMode::Anywhere => Self::GraphemeBoundary, } } } #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize)] #[serde(rename_all(deserialize = "kebab-case"))] struct StreampagerConfig { interface: StreampagerAlternateScreenMode, wrapping: StreampagerWrappingMode, show_ruler: bool, // TODO: Add an `quit-quickly-delay-seconds` floating point option or a // `quit-quickly-delay` option that takes a 's' or 'ms' suffix. Note that as // of this writing, floating point numbers do not work with `--config` } impl StreampagerConfig { fn streampager_interface_mode(&self) -> streampager::config::InterfaceMode { use StreampagerAlternateScreenMode::*; use streampager::config::InterfaceMode; match self.interface { // InterfaceMode::Direct not implemented FullScreenClearOutput => InterfaceMode::FullScreen, QuitIfOnePage => InterfaceMode::Hybrid, QuitQuicklyOrClearOutput => InterfaceMode::Delayed(std::time::Duration::from_secs(2)), } } } enum PagerConfig { Disabled, Builtin(StreampagerConfig), External(CommandNameAndArgs), } impl PagerConfig { fn from_config(config: &StackedConfig) -> Result<Self, ConfigGetError> { if matches!(config.get("ui.paginate")?, PaginationChoice::Never) { return Ok(Self::Disabled); }; let args: CommandNameAndArgs = config.get("ui.pager")?; if args.as_str() == Some(BUILTIN_PAGER_NAME) { Ok(Self::Builtin(config.get("ui.streampager")?)) } else { Ok(Self::External(args)) } } } impl Ui { pub fn null() -> Self { Self { quiet: true, pager: PagerConfig::Disabled, progress_indicator: false, formatter_factory: FormatterFactory::plain_text(), output: UiOutput::Null, } } pub fn with_config(config: &StackedConfig) -> Result<Self, CommandError> { let formatter_factory = prepare_formatter_factory(config, &io::stdout())?; Ok(Self { quiet: config.get("ui.quiet")?, formatter_factory, pager: PagerConfig::from_config(config)?, progress_indicator: config.get("ui.progress-indicator")?, output: UiOutput::new_terminal(), }) } pub fn reset(&mut self, config: &StackedConfig) -> Result<(), CommandError> { self.quiet = config.get("ui.quiet")?; self.pager = PagerConfig::from_config(config)?; self.progress_indicator = config.get("ui.progress-indicator")?; self.formatter_factory = prepare_formatter_factory(config, &io::stdout())?; Ok(()) } /// Switches the output to use the pager, if allowed. #[instrument(skip_all)] pub fn request_pager(&mut self) { if !matches!(&self.output, UiOutput::Terminal { stdout, .. } if stdout.is_terminal()) { return; } let new_output = match &self.pager { PagerConfig::Disabled => { return; } PagerConfig::Builtin(streampager_config) => { UiOutput::new_builtin_paged(streampager_config) .inspect_err(|err| { writeln!( self.warning_default(), "Failed to set up builtin pager: {err}", err = format_error_with_sources(err), ) .ok(); }) .ok() } PagerConfig::External(command_name_and_args) => { UiOutput::new_paged(command_name_and_args) .inspect_err(|err| { // The pager executable couldn't be found or couldn't be run writeln!( self.warning_default(), "Failed to spawn pager '{name}': {err}", name = command_name_and_args.split_name(), err = format_error_with_sources(err), ) .ok(); writeln!(self.hint_default(), "Consider using the `:builtin` pager.").ok(); }) .ok() } }; if let Some(output) = new_output { self.output = output; } } pub fn color(&self) -> bool { self.formatter_factory.is_color() } pub fn new_formatter<'output, W: Write + 'output>( &self, output: W, ) -> Box<dyn Formatter + 'output> { self.formatter_factory.new_formatter(output) } /// Locked stdout stream. pub fn stdout(&self) -> UiStdout<'_> { match &self.output { UiOutput::Terminal { stdout, .. } => UiStdout::Terminal(stdout.lock()), UiOutput::Paged { child_stdin, .. } => UiStdout::Paged(child_stdin), UiOutput::BuiltinPaged { out_wr, .. } => UiStdout::Builtin(out_wr), UiOutput::Null => UiStdout::Null(io::sink()), } } /// Creates a formatter for the locked stdout stream. /// /// Labels added to the returned formatter should be removed by caller. /// Otherwise the last color would persist. pub fn stdout_formatter(&self) -> Box<dyn Formatter + '_> { for_outputs!(UiStdout, self.stdout(), w => self.new_formatter(w)) } /// Locked stderr stream. pub fn stderr(&self) -> UiStderr<'_> { match &self.output { UiOutput::Terminal { stderr, .. } => UiStderr::Terminal(stderr.lock()), UiOutput::Paged { child_stdin, .. } => UiStderr::Paged(child_stdin), UiOutput::BuiltinPaged { err_wr, .. } => UiStderr::Builtin(err_wr), UiOutput::Null => UiStderr::Null(io::sink()), } } /// Creates a formatter for the locked stderr stream. pub fn stderr_formatter(&self) -> Box<dyn Formatter + '_> { for_outputs!(UiStderr, self.stderr(), w => self.new_formatter(w)) } /// Stderr stream to be attached to a child process. pub fn stderr_for_child(&self) -> io::Result<Stdio> { match &self.output { UiOutput::Terminal { .. } => Ok(Stdio::inherit()), UiOutput::Paged { child_stdin, .. } => Ok(duplicate_child_stdin(child_stdin)?.into()), UiOutput::BuiltinPaged { err_wr, .. } => Ok(err_wr.try_clone()?.into()), UiOutput::Null => Ok(Stdio::null()), } } /// Whether continuous feedback should be displayed for long-running /// operations pub fn use_progress_indicator(&self) -> bool { match &self.output { UiOutput::Terminal { stderr, .. } => self.progress_indicator && stderr.is_terminal(), UiOutput::Paged { .. } => false, UiOutput::BuiltinPaged { .. } => false, UiOutput::Null => false, } } pub fn progress_output(&self) -> Option<ProgressOutput<std::io::Stderr>> { self.use_progress_indicator() .then(ProgressOutput::for_stderr) } /// Writer to print an update that's not part of the command's main output. pub fn status(&self) -> Box<dyn Write + '_> { if self.quiet { Box::new(io::sink()) } else { Box::new(self.stderr()) } } /// A formatter to print an update that's not part of the command's main /// output. Returns `None` if `--quiet` was requested. pub fn status_formatter(&self) -> Option<Box<dyn Formatter + '_>> { (!self.quiet).then(|| self.stderr_formatter()) } /// Writer to print hint with the default "Hint: " heading. pub fn hint_default(&self) -> HeadingLabeledWriter<Box<dyn Formatter + '_>, &'static str> {