File size: 4,134 Bytes
d90101d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | use std::{fmt, io};
use colored::Colorize;
use forge_tracker::VERSION;
const BANNER: &str = include_str!("banner");
/// Renders messages into a styled box with border characters.
struct DisplayBox {
messages: Vec<String>,
}
impl DisplayBox {
/// Creates a new Box with the given messages.
fn new(messages: Vec<String>) -> Self {
Self { messages }
}
}
impl fmt::Display for DisplayBox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let visible_len = |s: &str| console::measure_text_width(s);
let width: usize = self
.messages
.iter()
.map(|s| visible_len(s))
.max()
.unwrap_or(0)
+ 4;
let top = format!("┌{}┐", "─".repeat(width.saturating_sub(2)));
let bottom = format!("└{}┘", "─".repeat(width.saturating_sub(2)));
let fmt_line = |s: &str| {
let padding = width.saturating_sub(4).saturating_sub(visible_len(s));
format!("│ {}{} │", s, " ".repeat(padding))
};
writeln!(f, "{}", top)?;
for msg in &self.messages {
writeln!(f, "{}", fmt_line(msg))?;
}
write!(f, "{}", bottom)
}
}
/// Displays the banner with version and command tips.
///
/// # Arguments
///
/// * `cli_mode` - If true, shows CLI-relevant commands. Both interactive and
/// CLI modes use `:` as the canonical command prefix.
///
/// # Environment Variables
///
/// * `FORGE_BANNER` - Optional custom banner text to display instead of the
/// default
pub fn display(cli_mode: bool) -> io::Result<()> {
// Check for custom banner via environment variable
let mut banner = std::env::var("FORGE_BANNER")
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| BANNER.to_string());
// Always show version
let version_label = ("Version:", VERSION);
// Build tips based on mode
let tips: Vec<(&str, &str)> = if cli_mode {
// CLI mode: only show relevant commands
vec![
("New conversation:", ":new"),
("Get started:", ":info, :conversation"),
("Switch model:", ":model"),
("Switch provider:", ":provider"),
("Switch agent:", ":<agent_name> e.g. :forge or :muse"),
]
} else {
// Interactive mode: show all commands
vec![
("New conversation:", ":new"),
("Get started:", ":info, :usage, :help, :conversation"),
("Switch model:", ":model"),
("Switch agent:", ":forge or :muse or :agent"),
("Update:", ":update"),
("Quit:", ":exit or <CTRL+D>"),
]
};
// Build labels array with version and tips
let labels: Vec<(&str, &str)> = std::iter::once(version_label).chain(tips).collect();
// Calculate the width of the longest label key for alignment
let max_width = labels.iter().map(|(key, _)| key.len()).max().unwrap_or(0);
// Add all lines with right-aligned label keys and their values
for (key, value) in &labels {
banner.push_str(
format!(
"\n{}{}",
format!("{key:>max_width$} ").dimmed(),
value.cyan()
)
.as_str(),
);
}
println!("{banner}\n");
// Encourage zsh integration after the banner
if !cli_mode {
display_zsh_encouragement();
}
Ok(())
}
/// Encourages users to use the zsh plugin for a better experience.
fn display_zsh_encouragement() {
let tip = DisplayBox::new(vec![
format!(
"{} {}",
"TIP:".bold().yellow(),
"For the best experience, use our zsh plugin!".bold()
),
format!(
"{} {} {}",
"·".dimmed(),
"Set up forge via our zsh plugin:".dimmed(),
"forge zsh setup".bold().green(),
),
format!(
"{} {} {}",
"·".dimmed(),
"Learn more:".dimmed(),
"https://forgecode.dev/docs/zsh-support".cyan()
),
]);
println!("{}", tip);
}
|