From 5c3a65e3d107f970e0e07b7abca73ca16be146a0 Mon Sep 17 00:00:00 2001 From: EmojiPati <49492351+EmojiPati@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:44:59 +0300 Subject: [PATCH 1/4] feat(rprompt): display reasoning effort next to model in zsh rprompt Adds the currently configured reasoning effort (e.g. HIGH, MEDIUM) to the right of the model in the ZSH right prompt. Follows the existing active/inactive (bright/dimmed) color pattern, renders a nerd-font lightbulb glyph when nerd fonts are enabled, and picks up session overrides set via the FORGE_REASONING__EFFORT env var. --- crates/forge_main/src/ui.rs | 6 +- crates/forge_main/src/zsh/rprompt.rs | 119 ++++++++++++++++++++++++++- crates/forge_main/src/zsh/style.rs | 2 + 3 files changed, 121 insertions(+), 6 deletions(-) diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 9967c7e317..1aaf241cf0 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -4363,7 +4363,7 @@ impl A + Send + Sync> UI .and_then(|str| ConversationId::from_str(str.as_str()).ok()); // Make IO calls in parallel - let (model_id, conversation) = tokio::join!( + let (model_id, conversation, reasoning_effort) = tokio::join!( async { self.api.get_session_config().await.map(|c| c.model) }, async { if let Some(cid) = cid { @@ -4371,7 +4371,8 @@ impl A + Send + Sync> UI } else { None } - } + }, + async { self.api.get_reasoning_effort().await.ok().flatten() } ); // Calculate total cost including related conversations @@ -4402,6 +4403,7 @@ impl A + Send + Sync> UI .model(model_id) .token_count(conversation.and_then(|conversation| conversation.token_count())) .cost(cost) + .reasoning_effort(reasoning_effort) .use_nerd_font(use_nerd_font); Some(rprompt.to_string()) diff --git a/crates/forge_main/src/zsh/rprompt.rs b/crates/forge_main/src/zsh/rprompt.rs index 58cbfd7813..a58d96618f 100644 --- a/crates/forge_main/src/zsh/rprompt.rs +++ b/crates/forge_main/src/zsh/rprompt.rs @@ -1,29 +1,32 @@ //! ZSH right prompt implementation. //! //! Provides the right prompt (RPROMPT) display for the ZSH shell integration, -//! showing agent name, model, and token count information. +//! showing agent name, model, token count and reasoning effort information. use std::fmt::{self, Display}; use convert_case::{Case, Casing}; use derive_setters::Setters; use forge_config::ForgeConfig; -use forge_domain::{AgentId, ModelId, TokenCount}; +use forge_domain::{AgentId, Effort, ModelId, TokenCount}; use super::style::{ZshColor, ZshStyle}; use crate::utils::humanize_number; -/// ZSH right prompt displaying agent, model, and token count. +/// ZSH right prompt displaying agent, model, token count and reasoning effort. /// /// Formats shell prompt information with appropriate colors: /// - Inactive state (no tokens): dimmed colors -/// - Active state (has tokens): bright white/cyan colors +/// - Active state (has tokens): bright white/cyan/yellow colors #[derive(Setters)] pub struct ZshRPrompt { agent: Option, model: Option, token_count: Option, cost: Option, + /// Currently configured reasoning effort level for the active model. + /// Rendered to the right of the model when set. + reasoning_effort: Option, /// Controls whether to render nerd font symbols. Defaults to `true`. #[setters(into)] use_nerd_font: bool, @@ -52,6 +55,7 @@ impl Default for ZshRPrompt { model: None, token_count: None, cost: None, + reasoning_effort: None, use_nerd_font: true, currency_symbol: "\u{f155}".to_string(), conversion_ratio: 1.0, @@ -61,6 +65,7 @@ impl Default for ZshRPrompt { const AGENT_SYMBOL: &str = "\u{f167a}"; const MODEL_SYMBOL: &str = "\u{ec19}"; +const REASONING_SYMBOL: &str = "\u{eb41}"; impl Display for ZshRPrompt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -121,12 +126,30 @@ impl Display for ZshRPrompt { write!(f, " {}", styled)?; } + // Add reasoning effort (rendered to the right of the model) + if let Some(ref effort) = self.reasoning_effort { + let effort_label = effort.to_string().to_uppercase(); + let effort_label = if self.use_nerd_font { + format!("{REASONING_SYMBOL} {}", effort_label) + } else { + effort_label + }; + let styled = if active { + effort_label.zsh().fg(ZshColor::YELLOW) + } else { + effort_label.zsh().fg(ZshColor::DIMMED) + }; + write!(f, " {}", styled)?; + } + Ok(()) } } #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; + use super::*; #[test] @@ -213,4 +236,92 @@ mod tests { let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %B%F{2}€0.01%f%b %F{134}\u{ec19} gpt-4%f"; assert_eq!(actual, expected); } + + #[test] + fn test_rprompt_with_reasoning_effort_active() { + // Active state (tokens > 0) renders reasoning effort in YELLOW to the + // right of the model. + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(Some(Effort::High)) + .to_string(); + + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} HIGH%f"; + assert_eq!(actual, expected); + } + + #[test] + fn test_rprompt_with_reasoning_effort_init_state() { + // Inactive state (no tokens) renders reasoning effort DIMMED. + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .reasoning_effort(Some(Effort::Medium)) + .to_string(); + + let expected = + " %B%F{240}\u{f167a} FORGE%f%b %F{240}\u{ec19} gpt-4%f %F{240}\u{eb41} MEDIUM%f"; + assert_eq!(actual, expected); + } + + #[test] + fn test_rprompt_with_reasoning_effort_without_nerdfonts() { + // With nerd fonts disabled the reasoning effort is rendered as plain + // uppercase text without any leading glyph. + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(Some(Effort::Low)) + .use_nerd_font(false) + .to_string(); + + let expected = " %B%F{15}FORGE%f%b %B%F{15}1.5k%f%b %F{134}gpt-4%f %F{3}LOW%f"; + assert_eq!(actual, expected); + } + + #[test] + fn test_rprompt_with_reasoning_effort_none_variant() { + // `Effort::None` is a valid explicit value; render it so the user can + // see that reasoning was deliberately disabled. + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(Some(Effort::None)) + .to_string(); + + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} NONE%f"; + assert_eq!(actual, expected); + } + + #[test] + fn test_rprompt_without_reasoning_effort_is_hidden() { + // When no reasoning effort is set, nothing is appended after the model. + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(None) + .to_string(); + + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f"; + assert_eq!(actual, expected); + } + + #[test] + fn test_rprompt_with_reasoning_effort_xhigh() { + // `Effort::XHigh` renders as the uppercase string "XHIGH". + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(Some(Effort::XHigh)) + .to_string(); + + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} XHIGH%f"; + assert_eq!(actual, expected); + } } diff --git a/crates/forge_main/src/zsh/style.rs b/crates/forge_main/src/zsh/style.rs index 5cc9fe4590..fbfe5f47ad 100644 --- a/crates/forge_main/src/zsh/style.rs +++ b/crates/forge_main/src/zsh/style.rs @@ -20,6 +20,8 @@ impl ZshColor { pub const CYAN: Self = Self(134); /// Green (color 2) pub const GREEN: Self = Self(2); + /// Yellow (color 3) + pub const YELLOW: Self = Self(3); /// Dimmed gray (color 240) pub const DIMMED: Self = Self(240); From 09228c7bb34b5b0e6ee20c85e8e7e5f73d7de392 Mon Sep 17 00:00:00 2001 From: EmojiPati <49492351+EmojiPati@users.noreply.github.com> Date: Mon, 20 Apr 2026 07:41:00 +0300 Subject: [PATCH 2/4] feat(rprompt): hide reasoning effort when explicitly set to none MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback, the \`Effort::None\` variant carries no useful information for the user (it means 'no reasoning' — same effective state as having reasoning unset), so the rprompt now suppresses it entirely instead of rendering the literal text 'NONE'. The corresponding test was updated to assert the new hidden behavior. --- crates/forge_main/src/zsh/rprompt.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/forge_main/src/zsh/rprompt.rs b/crates/forge_main/src/zsh/rprompt.rs index a58d96618f..f0f2ddfd98 100644 --- a/crates/forge_main/src/zsh/rprompt.rs +++ b/crates/forge_main/src/zsh/rprompt.rs @@ -126,8 +126,12 @@ impl Display for ZshRPrompt { write!(f, " {}", styled)?; } - // Add reasoning effort (rendered to the right of the model) - if let Some(ref effort) = self.reasoning_effort { + // Add reasoning effort (rendered to the right of the model). + // `Effort::None` is suppressed because it carries no useful information + // for the user to see in the prompt. + if let Some(ref effort) = self.reasoning_effort + && !matches!(effort, Effort::None) + { let effort_label = effort.to_string().to_uppercase(); let effort_label = if self.use_nerd_font { format!("{REASONING_SYMBOL} {}", effort_label) @@ -283,9 +287,9 @@ mod tests { } #[test] - fn test_rprompt_with_reasoning_effort_none_variant() { - // `Effort::None` is a valid explicit value; render it so the user can - // see that reasoning was deliberately disabled. + fn test_rprompt_with_reasoning_effort_none_variant_is_hidden() { + // `Effort::None` is semantically "no reasoning" and carries no display + // value, so the rprompt suppresses it entirely. let actual = ZshRPrompt::default() .agent(Some(AgentId::new("forge"))) .model(Some(ModelId::new("gpt-4"))) @@ -293,7 +297,7 @@ mod tests { .reasoning_effort(Some(Effort::None)) .to_string(); - let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} NONE%f"; + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f"; assert_eq!(actual, expected); } From d10e85b16719714b49b1d26a6f4158b29d14ace7 Mon Sep 17 00:00:00 2001 From: EmojiPati <49492351+EmojiPati@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:04:51 +0300 Subject: [PATCH 3/4] feat(rprompt): adapt reasoning effort label to terminal width Render the reasoning effort as a three-letter abbreviation (MIN, LOW, MED, HIG, XHI, MAX) when the terminal is narrower than 100 columns, and as the full uppercase label (MINIMAL, LOW, MEDIUM, HIGH, XHIGH, MAX) when wider. This keeps the prompt compact on small screens while preserving readability on full-screen setups. Width is sourced from the $COLUMNS env var propagated by the zsh shell plugin. Callers that do not supply a width default to the full-length label. --- crates/forge_main/src/ui.rs | 9 +++ crates/forge_main/src/zsh/rprompt.rs | 114 ++++++++++++++++++++++++++- shell-plugin/forge.theme.zsh | 2 +- 3 files changed, 122 insertions(+), 3 deletions(-) diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 1aaf241cf0..6605f56813 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -4393,6 +4393,14 @@ impl A + Send + Sync> UI .map(|val| val == "1") .unwrap_or(true); // Default to true + // Read terminal width from COLUMNS (propagated by the zsh shell plugin) + // so the rprompt can pick a compact or full-length reasoning effort + // label. Missing or unparseable values fall back to the full-length + // form in the renderer. + let terminal_width = std::env::var("COLUMNS") + .ok() + .and_then(|s| s.parse::().ok()); + let rprompt = ZshRPrompt::from_config(&self.config) .agent( std::env::var("_FORGE_ACTIVE_AGENT") @@ -4404,6 +4412,7 @@ impl A + Send + Sync> UI .token_count(conversation.and_then(|conversation| conversation.token_count())) .cost(cost) .reasoning_effort(reasoning_effort) + .terminal_width(terminal_width) .use_nerd_font(use_nerd_font); Some(rprompt.to_string()) diff --git a/crates/forge_main/src/zsh/rprompt.rs b/crates/forge_main/src/zsh/rprompt.rs index f0f2ddfd98..a859567f4f 100644 --- a/crates/forge_main/src/zsh/rprompt.rs +++ b/crates/forge_main/src/zsh/rprompt.rs @@ -2,6 +2,11 @@ //! //! Provides the right prompt (RPROMPT) display for the ZSH shell integration, //! showing agent name, model, token count and reasoning effort information. +//! +//! The reasoning effort label is rendered in one of two forms depending on +//! the available terminal width: a three-letter abbreviation (e.g. `MED`, +//! `HIG`) on narrow terminals and the full uppercase label (e.g. `MEDIUM`, +//! `HIGH`) on wider terminals. See [`WIDE_TERMINAL_THRESHOLD`]. use std::fmt::{self, Display}; @@ -18,6 +23,12 @@ use crate::utils::humanize_number; /// Formats shell prompt information with appropriate colors: /// - Inactive state (no tokens): dimmed colors /// - Active state (has tokens): bright white/cyan/yellow colors +/// +/// The reasoning effort label adapts to the available terminal width: on +/// narrow terminals (< [`WIDE_TERMINAL_THRESHOLD`] columns) it is rendered +/// as a three-letter abbreviation, otherwise the full uppercase label is +/// shown. When [`ZshRPrompt::terminal_width`] is unset the full-length form +/// is used as a safe default. #[derive(Setters)] pub struct ZshRPrompt { agent: Option, @@ -27,6 +38,11 @@ pub struct ZshRPrompt { /// Currently configured reasoning effort level for the active model. /// Rendered to the right of the model when set. reasoning_effort: Option, + /// Terminal width in columns, used to pick between the compact + /// three-letter label and the full-length uppercase label for + /// reasoning effort. When `None`, the prompt falls back to the + /// full-length form. + terminal_width: Option, /// Controls whether to render nerd font symbols. Defaults to `true`. #[setters(into)] use_nerd_font: bool, @@ -56,6 +72,7 @@ impl Default for ZshRPrompt { token_count: None, cost: None, reasoning_effort: None, + terminal_width: None, use_nerd_font: true, currency_symbol: "\u{f155}".to_string(), conversion_ratio: 1.0, @@ -67,6 +84,17 @@ const AGENT_SYMBOL: &str = "\u{f167a}"; const MODEL_SYMBOL: &str = "\u{ec19}"; const REASONING_SYMBOL: &str = "\u{eb41}"; +/// Terminal width (in columns) at which the reasoning effort label switches +/// from the compact three-letter form to the full uppercase label. +/// +/// Widths greater than or equal to this threshold render the full label +/// (e.g. `MEDIUM`, `HIGH`); widths below it collapse to the first three +/// characters (e.g. `MED`, `HIG`). The value is intentionally a coarse +/// static threshold — typical RPROMPT content is around 40-50 visible +/// cells, so 100 columns leaves enough room on the left for most LPROMPTs +/// and comfortable typing space once the full label is shown. +const WIDE_TERMINAL_THRESHOLD: usize = 100; + impl Display for ZshRPrompt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let active = *self.token_count.unwrap_or_default() > 0usize; @@ -128,11 +156,29 @@ impl Display for ZshRPrompt { // Add reasoning effort (rendered to the right of the model). // `Effort::None` is suppressed because it carries no useful information - // for the user to see in the prompt. + // for the user to see in the prompt. Below `WIDE_TERMINAL_THRESHOLD` + // columns the label collapses to its first three characters so the + // prompt stays compact on narrow terminals; above the threshold the + // full uppercase label is rendered for readability. if let Some(ref effort) = self.reasoning_effort && !matches!(effort, Effort::None) { - let effort_label = effort.to_string().to_uppercase(); + let is_wide = + self.terminal_width.unwrap_or(WIDE_TERMINAL_THRESHOLD) >= WIDE_TERMINAL_THRESHOLD; + // Use `chars().take(3).collect()` rather than `&label[..3]` to + // satisfy the `clippy::string_slice` lint that is denied in CI. + // `Effort` serializes as lowercase ASCII, so taking the first + // three chars is always well-defined. + let effort_label = if is_wide { + effort.to_string().to_uppercase() + } else { + effort + .to_string() + .chars() + .take(3) + .collect::() + .to_uppercase() + }; let effort_label = if self.use_nerd_font { format!("{REASONING_SYMBOL} {}", effort_label) } else { @@ -328,4 +374,68 @@ mod tests { let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} XHIGH%f"; assert_eq!(actual, expected); } + + #[test] + fn test_rprompt_reasoning_effort_narrow_terminal_uses_short_form() { + // Below the wide-terminal threshold, the reasoning effort collapses + // to the first three characters uppercased ("MEDIUM" -> "MED"). + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(Some(Effort::Medium)) + .terminal_width(Some(80)) + .to_string(); + + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} MED%f"; + assert_eq!(actual, expected); + } + + #[test] + fn test_rprompt_reasoning_effort_wide_terminal_uses_full_form() { + // At or above the wide-terminal threshold, the full uppercase label + // is rendered (e.g. "MEDIUM" rather than "MED"). + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(Some(Effort::Medium)) + .terminal_width(Some(120)) + .to_string(); + + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} MEDIUM%f"; + assert_eq!(actual, expected); + } + + #[test] + fn test_rprompt_reasoning_effort_at_threshold_is_full_form() { + // The threshold is inclusive: a width of exactly + // `WIDE_TERMINAL_THRESHOLD` columns renders the full label. + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(Some(Effort::High)) + .terminal_width(Some(WIDE_TERMINAL_THRESHOLD)) + .to_string(); + + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} HIGH%f"; + assert_eq!(actual, expected); + } + + #[test] + fn test_rprompt_reasoning_effort_short_form_minimal() { + // The longest variant name ("MINIMAL", 7 chars) must truncate to + // exactly three characters ("MIN") in the compact form. + let actual = ZshRPrompt::default() + .agent(Some(AgentId::new("forge"))) + .model(Some(ModelId::new("gpt-4"))) + .token_count(Some(TokenCount::Actual(1500))) + .reasoning_effort(Some(Effort::Minimal)) + .terminal_width(Some(80)) + .to_string(); + + let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} MIN%f"; + assert_eq!(actual, expected); + } } diff --git a/shell-plugin/forge.theme.zsh b/shell-plugin/forge.theme.zsh index fab0e5de6c..adfe56791b 100644 --- a/shell-plugin/forge.theme.zsh +++ b/shell-plugin/forge.theme.zsh @@ -18,7 +18,7 @@ function _forge_prompt_info() { [[ -n "$_FORGE_SESSION_MODEL" ]] && local -x FORGE_SESSION__MODEL_ID="$_FORGE_SESSION_MODEL" [[ -n "$_FORGE_SESSION_PROVIDER" ]] && local -x FORGE_SESSION__PROVIDER_ID="$_FORGE_SESSION_PROVIDER" [[ -n "$_FORGE_SESSION_REASONING_EFFORT" ]] && local -x FORGE_REASONING__EFFORT="$_FORGE_SESSION_REASONING_EFFORT" - _FORGE_CONVERSATION_ID=$_FORGE_CONVERSATION_ID _FORGE_ACTIVE_AGENT=$_FORGE_ACTIVE_AGENT "${forge_cmd[@]}" 2>/dev/null + _FORGE_CONVERSATION_ID=$_FORGE_CONVERSATION_ID _FORGE_ACTIVE_AGENT=$_FORGE_ACTIVE_AGENT COLUMNS=$COLUMNS "${forge_cmd[@]}" 2>/dev/null } # Right prompt: agent and model with token count (uses single forge prompt command) From cad98ee32cd6460d955415847f93b528fae72b3d Mon Sep 17 00:00:00 2001 From: EmojiPati <49492351+EmojiPati@users.noreply.github.com> Date: Tue, 21 Apr 2026 05:44:30 +0300 Subject: [PATCH 4/4] feat(rprompt): drop reasoning effort glyph from zsh rprompt Per review feedback in #3087, the reasoning effort is conceptually a property of the active model rather than a standalone segment, so it no longer needs its own nerd-font icon next to the model name. The label is still color-coded (YELLOW when active, DIMMED when inactive) and still adapts to terminal width (MED/HIG on narrow terminals, MEDIUM/HIGH on wide ones). - Removed the REASONING_SYMBOL constant and the nerd-font branch of the reasoning effort renderer in Display::fmt. - Updated test expectations to assert the glyph-free output. --- crates/forge_main/src/zsh/rprompt.rs | 31 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/crates/forge_main/src/zsh/rprompt.rs b/crates/forge_main/src/zsh/rprompt.rs index a859567f4f..9df786ac8b 100644 --- a/crates/forge_main/src/zsh/rprompt.rs +++ b/crates/forge_main/src/zsh/rprompt.rs @@ -82,7 +82,6 @@ impl Default for ZshRPrompt { const AGENT_SYMBOL: &str = "\u{f167a}"; const MODEL_SYMBOL: &str = "\u{ec19}"; -const REASONING_SYMBOL: &str = "\u{eb41}"; /// Terminal width (in columns) at which the reasoning effort label switches /// from the compact three-letter form to the full uppercase label. @@ -179,11 +178,6 @@ impl Display for ZshRPrompt { .collect::() .to_uppercase() }; - let effort_label = if self.use_nerd_font { - format!("{REASONING_SYMBOL} {}", effort_label) - } else { - effort_label - }; let styled = if active { effort_label.zsh().fg(ZshColor::YELLOW) } else { @@ -298,7 +292,8 @@ mod tests { .reasoning_effort(Some(Effort::High)) .to_string(); - let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} HIGH%f"; + let expected = + " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}HIGH%f"; assert_eq!(actual, expected); } @@ -311,15 +306,14 @@ mod tests { .reasoning_effort(Some(Effort::Medium)) .to_string(); - let expected = - " %B%F{240}\u{f167a} FORGE%f%b %F{240}\u{ec19} gpt-4%f %F{240}\u{eb41} MEDIUM%f"; + let expected = " %B%F{240}\u{f167a} FORGE%f%b %F{240}\u{ec19} gpt-4%f %F{240}MEDIUM%f"; assert_eq!(actual, expected); } #[test] fn test_rprompt_with_reasoning_effort_without_nerdfonts() { - // With nerd fonts disabled the reasoning effort is rendered as plain - // uppercase text without any leading glyph. + // Nerd fonts disabled: agent and model lose their glyph prefixes; + // the reasoning effort remains as a plain uppercase color-coded label. let actual = ZshRPrompt::default() .agent(Some(AgentId::new("forge"))) .model(Some(ModelId::new("gpt-4"))) @@ -371,7 +365,8 @@ mod tests { .reasoning_effort(Some(Effort::XHigh)) .to_string(); - let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} XHIGH%f"; + let expected = + " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}XHIGH%f"; assert_eq!(actual, expected); } @@ -387,7 +382,8 @@ mod tests { .terminal_width(Some(80)) .to_string(); - let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} MED%f"; + let expected = + " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}MED%f"; assert_eq!(actual, expected); } @@ -403,7 +399,8 @@ mod tests { .terminal_width(Some(120)) .to_string(); - let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} MEDIUM%f"; + let expected = + " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}MEDIUM%f"; assert_eq!(actual, expected); } @@ -419,7 +416,8 @@ mod tests { .terminal_width(Some(WIDE_TERMINAL_THRESHOLD)) .to_string(); - let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} HIGH%f"; + let expected = + " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}HIGH%f"; assert_eq!(actual, expected); } @@ -435,7 +433,8 @@ mod tests { .terminal_width(Some(80)) .to_string(); - let expected = " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}\u{eb41} MIN%f"; + let expected = + " %B%F{15}\u{f167a} FORGE%f%b %B%F{15}1.5k%f%b %F{134}\u{ec19} gpt-4%f %F{3}MIN%f"; assert_eq!(actual, expected); } }