1use std::borrow::Cow;
2
3use owo_colors::OwoColorize;
4use tokio::task;
5
6use crate::{config::DevServerConfig, startup::Application};
7
8enum ValueTone {
9 Primary,
10 Success,
11 Warning,
12 Danger,
13 Accent,
14 Muted,
15}
16
17pub fn print_startup_summary(config: &DevServerConfig, app: &Application) {
18 let title = "WEB DEV SERVER";
19 let border = "=".repeat(title.len() + 8);
20
21 println!("{}", border.clone().bright_black());
22 println!(" {}", title.cyan().bold());
23 println!("{}", border.bright_black());
24
25 let address_primary = app.primary_url();
26 let address_alt = format!("http://localhost:{}", app.port());
27 let base_dir = Cow::Owned(app.base_dir().display().to_string());
28 let diff_mode = if app.diff_mode() {
29 Cow::Borrowed("ENABLED")
30 } else {
31 Cow::Borrowed("disabled")
32 };
33 let watching = if config.diff_mode {
34 Cow::Borrowed("Diff HTML/CSS updates")
35 } else {
36 Cow::Borrowed("Full page reloads")
37 };
38 let browser = if config.no_open_browser {
39 Cow::Borrowed("Manual (--no-open-browser)")
40 } else {
41 Cow::Borrowed("Auto-open on start")
42 };
43
44 let rows: Vec<(&str, Cow<'_, str>, ValueTone)> = vec![
45 ("Address", Cow::Owned(address_primary), ValueTone::Primary),
46 ("Alt", Cow::Owned(address_alt), ValueTone::Muted),
47 ("Base Dir", base_dir, ValueTone::Accent),
48 (
49 "Diff Mode",
50 diff_mode,
51 if app.diff_mode() {
52 ValueTone::Success
53 } else {
54 ValueTone::Danger
55 },
56 ),
57 ("Watching", watching, ValueTone::Warning),
58 ("Browser", browser, ValueTone::Accent),
59 (
60 "Exit",
61 Cow::Borrowed("Press Ctrl+C to stop"),
62 ValueTone::Accent,
63 ),
64 ];
65
66 let label_width = rows
67 .iter()
68 .map(|(label, _, _)| label.len())
69 .max()
70 .unwrap_or(0)
71 + 1;
72
73 for (label, value, tone) in rows {
74 let padded_label = format!("{label:<label_width$}:", label_width = label_width);
75 let colored_label = format!("{}", padded_label.bright_blue().bold());
76 let colored_value = colorize(value.as_ref(), tone);
77
78 println!(" {} {}", colored_label, colored_value);
79 }
80
81 println!();
82 println!(
83 " {} {}",
84 "Serving".bright_black(),
85 app.base_dir().display().to_string().bright_black()
86 );
87 if config.no_open_browser {
88 println!(
89 " {}",
90 "Browser launch disabled (--no-open-browser)."
91 .bright_black()
92 .italic()
93 );
94 } else {
95 println!(
96 " {}",
97 "Copy the address above if your browser did not open automatically."
98 .bright_black()
99 .italic()
100 );
101 }
102 println!(
103 " {}",
104 "Leave this terminal open to keep the live server running."
105 .bright_black()
106 .italic()
107 );
108 println!();
109}
110
111fn colorize(value: &str, tone: ValueTone) -> String {
112 match tone {
113 ValueTone::Primary => value.bold().bright_white().to_string(),
114 ValueTone::Success => value.bold().bright_green().to_string(),
115 ValueTone::Warning => value.to_string().bright_yellow().to_string(),
116 ValueTone::Danger => value.bold().bright_red().to_string(),
117 ValueTone::Accent => value.to_string().bright_cyan().to_string(),
118 ValueTone::Muted => value.to_string().dimmed().to_string(),
119 }
120}
121
122pub fn launch_browser(url: String) {
123 task::spawn(async move {
124 match task::spawn_blocking(move || open::that(url)).await {
125 Ok(Ok(())) => {}
126 Ok(Err(error)) => {
127 eprintln!("[web-dev-server] failed to open browser: {error}");
128 }
129 Err(error) => {
130 eprintln!("[web-dev-server] browser task join error: {error}");
131 }
132 }
133 });
134}