Repository: gitbutlerapp/gitbutler
File: crates/but/src/utils/output_channel.rs (Rust)

Code before:
    /// Create a new instance to output with `format` (advisory), which affects where it prints to.
    /// The `use_pager` parameter controls whether a pager should be created.
    ///
    /// It's configured to print to stdout unless [`OutputFormat::Json`] is used, then it prints everything
    /// to a `/dev/null` equivalent, so callers never have to worry if they interleave JSON with other output.
    pub fn new_with_optional_pager(format: OutputFormat, use_pager: bool) -> Self {
        OutputChannel {
            format,
            stdout: std::io::stdout(),
            pager: if !matches!(format, OutputFormat::Human)
                || std::env::var_os("NOPAGER").is_some()
                || !use_pager
            {
                None
            } else {
                let pager = minus::Pager::new();
                let msg = "can talk to newly created pager";
                pager.set_exit_strategy(ExitStrategy::PagerQuit).expect(msg);
                pager.set_prompt("GitButler").expect(msg);
                Some(pager)
            },
        }
    }

    /// Like [`Self::new_with_pager`], but will never create a pager or write JSON.
    /// Use this if a second instance of a channel is needed, and the first one could have a pager.
    pub fn new_without_pager_non_json(format: OutputFormat) -> Self {
        OutputChannel {
            format: match format {
                OutputFormat::Human | OutputFormat::Shell | OutputFormat::None => format,
                OutputFormat::Json => OutputFormat::None,
            },
            stdout: std::io::stdout(),
            pager: None,
        }
    }
}

impl Drop for OutputChannel {
    fn drop(&mut self) {
        if let Some(pager) = self.pager.take() {
            minus::page_all(pager).ok();
        }
    }
}


Review comment (suggestion):
I think there is value in unifying the prompt, which is why I think these functions should call `prompt()` internally with a customised prompt, followed by the parsing they need to do.

Right now, there are two prompting styles, and I don't think these are needed. I would be nice if you could pick one to make prompting consistent.

Code after:
        write!(self, "> ")?;
        std::io::Write::flush(&mut self.out.stdout)?;

        let mut input = String::new();
        self.stdin.read_line(&mut input)?;
        let input = input.trim().to_owned();
        if input.is_empty() {
            return Ok(None);
        }
        Ok(Some(input))
    }

    /// Prompt for y/n confirmation with a default value.
    /// Automatically appends `[Y/n]` or `[y/N]` based on the default.
    /// Empty input returns the default. Input starting with 'y'/'Y' returns Yes, anything else returns No.
    pub fn confirm(
        &mut self,
        prompt: impl AsRef<str>,
        default: ConfirmDefault,
    ) -> anyhow::Result<Confirm> {
        use std::fmt::Write;
        let suffix = match default {
            ConfirmDefault::Yes => "[Y/n]",
            ConfirmDefault::No => "[y/N]",
        };
        write!(self, "{} {}: ", prompt.as_ref(), suffix)?;
        std::io::Write::flush(&mut self.out.stdout)?;

        let mut input = String::new();
        self.stdin.read_line(&mut input)?;
        let input = input.trim().to_lowercase();

        if input.is_empty() {
            return Ok(match default {
                ConfirmDefault::Yes => Confirm::Yes,
                ConfirmDefault::No => Confirm::No,
            });
        }

        if input.starts_with('y') {
            Ok(Confirm::Yes)
        } else {
            Ok(Confirm::No)
        }
    }

    /// Prompt for y/n confirmation without a default.
    /// Automatically appends `[y/n]` to the prompt.
    /// Returns `NoInput` if the user provides empty input.
    pub fn confirm_no_default(
        &mut self,