From fc9a2f835ab0086d29985171e09527ab6653aabf Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Sun, 11 Jan 2026 07:42:02 +0800 Subject: [PATCH] Fix streaming markdown code fence detection bug The code fence (```) was not being properly detected during streaming, causing it to be rendered as inline code instead of a code block. Root cause: When buffering a code fence after seeing ```, the code was returning early for ALL characters including newlines. This meant handle_newline() was never called and block_state was never set to BlockState::CodeBlock. Fixes: - Don't return early for newlines when buffering code fence, allow them to fall through to handle_newline() - Support indented code fences (up to 3 spaces per CommonMark spec) by using trim_start() when checking for ``` at line start --- crates/g3-cli/src/streaming_markdown.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/g3-cli/src/streaming_markdown.rs b/crates/g3-cli/src/streaming_markdown.rs index 80a684a..1cda99c 100644 --- a/crates/g3-cli/src/streaming_markdown.rs +++ b/crates/g3-cli/src/streaming_markdown.rs @@ -197,13 +197,25 @@ impl StreamingMarkdownFormatter { return; } + // If we're already buffering a code fence (```), continue buffering until newline + // This handles the language identifier after ``` (e.g., ```rust) + let trimmed = self.current_line.trim_start(); + if trimmed.starts_with("```") && ch != '\n' { + // Continue buffering non-newline characters + self.current_line.push(ch); + return; + } + // If ch == '\n', fall through to the newline handler below + if ch == '`' { self.current_line.push(ch); // Check if this might be starting a code fence - if self.current_line.starts_with("```") { + let trimmed = self.current_line.trim_start(); + if trimmed.starts_with("```") { // Don't emit yet - wait for the full fence line - } else if self.current_line == "`" || self.current_line == "``" { + } else if trimmed == "`" || trimmed == "``" { // Might become a fence, keep buffering + // (current_line may have leading whitespace) } return; } else if ch == '>' && self.current_line.is_empty() { @@ -399,8 +411,11 @@ impl StreamingMarkdownFormatter { /// Handle a newline character. fn handle_newline(&mut self) { // Check if we were building a code fence - if self.current_line.starts_with("```") { - let lang = self.current_line[3..].trim().to_string(); + // Support indented code fences (up to 3 spaces per CommonMark spec) + let trimmed = self.current_line.trim_start(); + let leading_spaces = self.current_line.len() - trimmed.len(); + if trimmed.starts_with("```") && leading_spaces <= 3 { + let lang = trimmed[3..].trim().to_string(); let lang = if lang.is_empty() { None } else { Some(lang) }; self.block_state = BlockState::CodeBlock { lang,