From eeee2bb817488ebe8bce0ef782c0b836b0f93d63 Mon Sep 17 00:00:00 2001 From: Tushar Date: Wed, 8 Apr 2026 13:07:40 +0530 Subject: [PATCH 1/2] feat(ui): display resolved config as highlighted toml --- Cargo.lock | 14 ++++ README.md | 3 +- crates/forge_display/Cargo.toml | 2 + crates/forge_display/src/code.rs | 32 +++++++- crates/forge_display/src/lib.rs | 3 +- crates/forge_main/Cargo.toml | 1 + crates/forge_main/src/built_in_commands.json | 9 +- crates/forge_main/src/cli.rs | 3 - crates/forge_main/src/model.rs | 5 -- crates/forge_main/src/ui.rs | 86 +++----------------- shell-plugin/lib/actions/core.zsh | 6 -- shell-plugin/lib/dispatcher.zsh | 5 +- 12 files changed, 64 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9b0b0b366..d21f7e55de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2019,6 +2019,8 @@ dependencies = [ "strip-ansi-escapes", "syntect", "termimad", + "terminal-colorsaurus", + "two-face", ] [[package]] @@ -2203,6 +2205,7 @@ dependencies = [ "tiny_http", "tokio", "tokio-stream", + "toml_edit 0.25.10+spec-1.1.0", "tracing", "update-informer", "url", @@ -6968,6 +6971,17 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "two-face" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b285c51f8a6ade109ed4566d33ac4fb289fb5d6cf87ed70908a5eaf65e948e34" +dependencies = [ + "serde", + "serde_derive", + "syntect", +] + [[package]] name = "typeid" version = "1.0.3" diff --git a/README.md b/README.md index 4e98e38c4c..8d28f37e3f 100644 --- a/README.md +++ b/README.md @@ -325,8 +325,7 @@ Some commands change settings for the current session only. Others persist to yo # View & edit config :info # Show current session info (model, agent, conversation ID) -:env # Show environment and provider info -:config # List current configuration values +:config # Display effective resolved configuration in TOML format :config-edit # Open config file in $EDITOR (alias: :ce) :tools # List available tools for the current agent :skill # List available skills diff --git a/crates/forge_display/Cargo.toml b/crates/forge_display/Cargo.toml index fc722d45d9..2a11aca5fa 100644 --- a/crates/forge_display/Cargo.toml +++ b/crates/forge_display/Cargo.toml @@ -13,6 +13,8 @@ console.workspace = true regex.workspace = true termimad.workspace = true syntect.workspace = true +terminal-colorsaurus = "1.0.3" +two-face = "0.5.1" [dev-dependencies] insta.workspace = true diff --git a/crates/forge_display/src/code.rs b/crates/forge_display/src/code.rs index 7078b59768..8c77368734 100644 --- a/crates/forge_display/src/code.rs +++ b/crates/forge_display/src/code.rs @@ -4,6 +4,8 @@ use syntect::easy::HighlightLines; use syntect::highlighting::ThemeSet; use syntect::parsing::SyntaxSet; use syntect::util::as_24_bit_terminal_escaped; +use terminal_colorsaurus::{QueryOptions, ThemeMode, theme_mode}; +use two_face::theme::EmbeddedThemeName; /// Loads and caches syntax highlighting resources. #[derive(Clone)] @@ -14,20 +16,40 @@ pub struct SyntaxHighlighter { impl Default for SyntaxHighlighter { fn default() -> Self { + // Use two-face's extended syntax set which includes TOML, Rust, Python, etc. Self { - syntax_set: Arc::new(SyntaxSet::load_defaults_newlines()), - theme_set: Arc::new(ThemeSet::load_defaults()), + syntax_set: Arc::new(two_face::syntax::extra_newlines()), + theme_set: Arc::new(two_face::theme::extra().into()), } } } impl SyntaxHighlighter { - fn highlight(&self, code: &str, lang: &str) -> String { + /// Detects whether the terminal is using a dark or light background. + fn is_dark_theme() -> bool { + match theme_mode(QueryOptions::default()) { + Ok(ThemeMode::Light) => false, + Ok(ThemeMode::Dark) | Err(_) => true, + } + } + + /// Syntax-highlights `code` for the given language token (e.g. `"toml"`, + /// `"rust"`), returning an ANSI-escaped string ready for terminal output. + /// + /// The theme is chosen automatically based on the terminal background + /// (dark → `base16-ocean.dark`, light → `InspiredGitHub`). Falls back to + /// plain text if the language is unrecognised. + pub fn highlight(&self, code: &str, lang: &str) -> String { let syntax = self .syntax_set .find_syntax_by_token(lang) .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()); - let theme = &self.theme_set.themes["base16-ocean.dark"]; + let theme_name = if Self::is_dark_theme() { + EmbeddedThemeName::Base16OceanDark + } else { + EmbeddedThemeName::InspiredGithub + }; + let theme = &self.theme_set.themes[theme_name.as_name()]; let mut hl = HighlightLines::new(syntax, theme); code.lines() @@ -127,6 +149,7 @@ impl CodeBlockParser { } } + #[cfg(test)] mod tests { use pretty_assertions::assert_eq; @@ -263,4 +286,5 @@ mod tests { assert!(actual1.contains("let x = 1")); assert!(actual2.contains("print('hello')")); } + } diff --git a/crates/forge_display/src/lib.rs b/crates/forge_display/src/lib.rs index 2af7d2c955..41a4c61189 100644 --- a/crates/forge_display/src/lib.rs +++ b/crates/forge_display/src/lib.rs @@ -1,8 +1,9 @@ -mod code; +pub mod code; pub mod diff; pub mod grep; pub mod markdown; +pub use code::SyntaxHighlighter; pub use diff::DiffFormat; pub use grep::GrepFormat; pub use markdown::MarkdownFormat; diff --git a/crates/forge_main/Cargo.toml b/crates/forge_main/Cargo.toml index 9357f0da16..d3d4d472f8 100644 --- a/crates/forge_main/Cargo.toml +++ b/crates/forge_main/Cargo.toml @@ -42,6 +42,7 @@ tracing.workspace = true chrono.workspace = true serde_json.workspace = true serde.workspace = true +toml_edit.workspace = true strum.workspace = true strum_macros.workspace = true diff --git a/crates/forge_main/src/built_in_commands.json b/crates/forge_main/src/built_in_commands.json index 2d6b45ef30..c574c6f8a0 100644 --- a/crates/forge_main/src/built_in_commands.json +++ b/crates/forge_main/src/built_in_commands.json @@ -4,11 +4,10 @@ "description": "Print session information [alias: i]" }, { - "command": "env", - "description": "Display environment information [alias: e]" + "command": "config", + "description": "Display effective resolved configuration in TOML format" }, { - "command": "config-model", "description": "Switch the models [alias: cm]" }, { @@ -35,10 +34,6 @@ "command": "config-suggest-model", "description": "Set the model used for command suggestion generation [alias: csm]" }, - { - "command": "config", - "description": "List current configuration values" - }, { "command": "config-edit", "description": "Open the global forge config file (~/forge/.forge.toml) in an editor [alias: ce]" diff --git a/crates/forge_main/src/cli.rs b/crates/forge_main/src/cli.rs index de3285fdaa..e90049cb08 100644 --- a/crates/forge_main/src/cli.rs +++ b/crates/forge_main/src/cli.rs @@ -103,9 +103,6 @@ pub enum TopLevelCommand { porcelain: bool, }, - /// Display environment information. - Env, - /// Get, set, or list configuration values. Config(ConfigCommandGroup), diff --git a/crates/forge_main/src/model.rs b/crates/forge_main/src/model.rs index 83dd9f88cd..61abc4e712 100644 --- a/crates/forge_main/src/model.rs +++ b/crates/forge_main/src/model.rs @@ -257,7 +257,6 @@ impl ForgeCommandManager { "/compact" => Ok(SlashCommand::Compact), "/new" => Ok(SlashCommand::New), "/info" => Ok(SlashCommand::Info), - "/env" => Ok(SlashCommand::Env), "/usage" => Ok(SlashCommand::Usage), "/exit" => Ok(SlashCommand::Exit), "/update" => Ok(SlashCommand::Update), @@ -361,9 +360,6 @@ pub enum SlashCommand { /// Display usage information (tokens & requests). #[strum(props(usage = "Shows usage information (tokens & requests)"))] Usage, - /// Display environment information. - #[strum(props(usage = "Display environment information"))] - Env, /// Exit the application without any further action. #[strum(props(usage = "Exit the application"))] Exit, @@ -464,7 +460,6 @@ impl SlashCommand { SlashCommand::Message(_) => "message", SlashCommand::Update => "update", SlashCommand::Info => "info", - SlashCommand::Env => "env", SlashCommand::Usage => "usage", SlashCommand::Exit => "exit", SlashCommand::Forge => "forge", diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 08aa510f53..66288447e4 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -555,10 +555,6 @@ impl A + Send + Sync> UI self.on_info(porcelain, conversation_id).await?; return Ok(()); } - TopLevelCommand::Env => { - self.on_env().await?; - return Ok(()); - } TopLevelCommand::Banner => { banner::display(true)?; return Ok(()); @@ -1447,69 +1443,24 @@ impl A + Send + Sync> UI /// Lists current configuration values async fn on_show_config(&mut self, porcelain: bool) -> anyhow::Result<()> { - let model = self - .get_agent_model(None) - .await - .map(|m| m.as_str().to_string()); - let model = model.unwrap_or_else(|| markers::EMPTY.to_string()); - let provider = self - .get_provider(None) - .await - .ok() - .map(|p| p.id.to_string()) - .unwrap_or_else(|| markers::EMPTY.to_string()); - let commit_config = self.api.get_commit_config().await.ok().flatten(); - let commit_provider = commit_config - .as_ref() - .map(|c| c.provider.to_string()) - .unwrap_or_else(|| markers::EMPTY.to_string()); - let commit_model = commit_config - .as_ref() - .map(|c| c.model.as_str().to_string()) - .unwrap_or_else(|| markers::EMPTY.to_string()); - - let suggest_config = self.api.get_suggest_config().await.ok().flatten(); - let suggest_provider = suggest_config - .as_ref() - .map(|c| c.provider.to_string()) - .unwrap_or_else(|| markers::EMPTY.to_string()); - let suggest_model = suggest_config - .as_ref() - .map(|c| c.model.as_str().to_string()) - .unwrap_or_else(|| markers::EMPTY.to_string()); + // Get the effective resolved config + let config = &self.config; - let reasoning_effort = self - .api - .get_reasoning_effort() - .await - .ok() - .flatten() - .map(|e| e.to_string()) - .unwrap_or_else(|| markers::EMPTY.to_string()); - - let info = Info::new() - .add_title("SESSION") - .add_key_value("Model", model) - .add_key_value("Provider", provider) - .add_title("COMMIT") - .add_key_value("Model", commit_model) - .add_key_value("Provider", commit_provider) - .add_title("SUGGEST") - .add_key_value("Model", suggest_model) - .add_key_value("Provider", suggest_provider) - .add_title("REASONING") - .add_key_value("Effort", reasoning_effort); + // Serialize to TOML pretty format + let config_toml = toml_edit::ser::to_string_pretty(config) + .map_err(|e| anyhow::anyhow!("Failed to serialize config: {}", e))?; if porcelain { - self.writeln( - Porcelain::from(&info) - .into_long() - .drop_col(0) - .uppercase_headers(), - )?; + // For porcelain mode, output raw TOML without highlighting + self.writeln(config_toml)?; } else { - self.writeln(info)?; + // For human-readable mode, add a title and syntax-highlight the TOML + self.writeln("\nCONFIGURATION\n".bold().dimmed())?; + let highlighted = + forge_display::SyntaxHighlighter::default().highlight(&config_toml, "toml"); + self.writeln(highlighted)?; } + Ok(()) } @@ -1680,14 +1631,6 @@ impl A + Send + Sync> UI Ok(()) } - async fn on_env(&mut self) -> anyhow::Result<()> { - let env = self.api.environment(); - let config = &self.config; - let info = Info::from(&env).extend(Info::from(config)); - self.writeln(info)?; - Ok(()) - } - /// Generate ZSH plugin script async fn on_zsh_plugin(&self) -> anyhow::Result<()> { let plugin = crate::zsh::generate_zsh_plugin()?; @@ -1990,9 +1933,6 @@ impl A + Send + Sync> UI SlashCommand::Info => { self.on_info(false, self.state.conversation_id).await?; } - SlashCommand::Env => { - self.on_env().await?; - } SlashCommand::Usage => { self.on_usage().await?; } diff --git a/shell-plugin/lib/actions/core.zsh b/shell-plugin/lib/actions/core.zsh index 57decbb696..e8da1dc8db 100644 --- a/shell-plugin/lib/actions/core.zsh +++ b/shell-plugin/lib/actions/core.zsh @@ -41,12 +41,6 @@ function _forge_action_info() { fi } -# Action handler: Show environment info -function _forge_action_env() { - echo - _forge_exec env -} - # Action handler: Dump conversation function _forge_action_dump() { local input_text="$1" diff --git a/shell-plugin/lib/dispatcher.zsh b/shell-plugin/lib/dispatcher.zsh index 67f46340f9..2c58fa5799 100644 --- a/shell-plugin/lib/dispatcher.zsh +++ b/shell-plugin/lib/dispatcher.zsh @@ -148,9 +148,6 @@ function forge-accept-line() { info|i) _forge_action_info ;; - env|e) - _forge_action_env - ;; dump|d) _forge_action_dump "$input_text" ;; @@ -190,7 +187,7 @@ function forge-accept-line() { tools|t) _forge_action_tools ;; - config) + config|env|e) _forge_action_config ;; config-edit|ce) From 9a2343e81540a872c5907545de344a3a0a051dba Mon Sep 17 00:00:00 2001 From: Tushar Date: Wed, 8 Apr 2026 13:29:12 +0530 Subject: [PATCH 2/2] refactor(codeblockparser): restrict test blocks accessor visibility --- crates/forge_display/src/code.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/forge_display/src/code.rs b/crates/forge_display/src/code.rs index 8c77368734..66f1dd1743 100644 --- a/crates/forge_display/src/code.rs +++ b/crates/forge_display/src/code.rs @@ -135,7 +135,7 @@ impl CodeBlockParser { /// Get the extracted code blocks. #[cfg(test)] - pub fn blocks(&self) -> &[CodeBlock] { + pub(crate) fn blocks(&self) -> &[CodeBlock] { &self.blocks } @@ -149,7 +149,6 @@ impl CodeBlockParser { } } - #[cfg(test)] mod tests { use pretty_assertions::assert_eq; @@ -286,5 +285,4 @@ mod tests { assert!(actual1.contains("let x = 1")); assert!(actual2.contains("print('hello')")); } - }