Skip to content

fix: Fixed markdown verbose printing too large outputs#68

Merged
rs545837 merged 1 commit intoAtaraxy-Labs:mainfrom
Benjas333:fix-markdown-verbose
Apr 11, 2026
Merged

fix: Fixed markdown verbose printing too large outputs#68
rs545837 merged 1 commit intoAtaraxy-Labs:mainfrom
Benjas333:fix-markdown-verbose

Conversation

@Benjas333
Copy link
Copy Markdown
Contributor

@Benjas333 Benjas333 commented Apr 9, 2026

Changed

  • sem diff --format markdown now uses TextDiff to reduce the output length.

Decided to open a PR so that when you can review #65, you can decide to accept or deny this PR.
Closes #65

Before

crates/sem-cli/src/formatters/markdown.rs

Status Type Name
Δ function format_markdown

format_markdown

- pub fn format_markdown(result: &DiffResult, verbose: bool) -> String {
-     if result.changes.is_empty() {
-         return "No semantic changes detected.".to_string();
-     }
-
-     let mut lines: Vec<String> = Vec::new();
-
-     // Group changes by file (BTreeMap for sorted output)
-     let mut by_file: BTreeMap<&str, Vec<usize>> = BTreeMap::new();
-     for (i, change) in result.changes.iter().enumerate() {
-         by_file.entry(&change.file_path).or_default().push(i);
-     }
-
-     for (file_path, indices) in &by_file {
-         lines.push(format!("### {file_path}"));
-         lines.push(String::new());
-         lines.push("| Status | Type | Name |".to_string());
-         lines.push("|--------|------|------|".to_string());
-
-         let mut post_table: Vec<String> = Vec::new();
-
-         for &idx in indices {
-             let change = &result.changes[idx];
-             let status = match change.change_type {
-                 ChangeType::Added => "+",
-                 ChangeType::Deleted => "-",
-                 ChangeType::Modified => {
-                     if change.structural_change == Some(false) {
-                         "~"
-                     } else {
-                         "Δ"
-                     }
-                 }
-                 ChangeType::Moved => "→",
-                 ChangeType::Renamed => "↻",
-             };
-
-             let name_display = if let Some(ref old_name) = change.old_entity_name {
-                 format!("{old_name} -> {}", change.entity_name)
-             } else {
-                 change.entity_name.clone()
-             };
-             lines.push(format!(
-                 "| {} | {} | {} |",
-                 status, change.entity_type, name_display
-             ));
-
-             // Show content diff
-             if verbose {
-                 match change.change_type {
-                     ChangeType::Added => {
-                         if let Some(ref content) = change.after_content {
-                             post_table.push(String::new());
-                             post_table.push(format!("**`{}`**", change.entity_name));
-                             post_table.push("```diff".to_string());
-                             for line in content.lines() {
-                                 post_table.push(format!("+ {line}"));
-                             }
-                             post_table.push("```".to_string());
-                         }
-                     }
-                     ChangeType::Deleted => {
-                         if let Some(ref content) = change.before_content {
-                             post_table.push(String::new());
-                             post_table.push(format!("**`{}`**", change.entity_name));
-                             post_table.push("```diff".to_string());
-                             for line in content.lines() {
-                                 post_table.push(format!("- {line}"));
-                             }
-                             post_table.push("```".to_string());
-                         }
-                     }
-                     ChangeType::Modified => {
-                         if let (Some(before), Some(after)) =
-                             (&change.before_content, &change.after_content)
-                         {
-                             post_table.push(String::new());
-                             post_table.push(format!("**`{}`**", change.entity_name));
-                             post_table.push("```diff".to_string());
-                             for line in before.lines() {
-                                 post_table.push(format!("- {line}"));
-                             }
-                             for line in after.lines() {
-                                 post_table.push(format!("+ {line}"));
-                             }
-                             post_table.push("```".to_string());
-                         }
-                     }
-                     _ => {}
-                 }
-             } else if change.change_type == ChangeType::Modified {
-                 if let (Some(before), Some(after)) =
-                     (&change.before_content, &change.after_content)
-                 {
-                     let before_lines: Vec<&str> = before.lines().collect();
-                     let after_lines: Vec<&str> = after.lines().collect();
-
-                     if before_lines.len() <= 3 && after_lines.len() <= 3 {
-                         post_table.push(String::new());
-                         post_table.push(format!("**`{}`**", change.entity_name));
-                         post_table.push("```diff".to_string());
-                         for line in &before_lines {
-                             post_table.push(format!("- {}", line.trim()));
-                         }
-                         for line in &after_lines {
-                             post_table.push(format!("+ {}", line.trim()));
-                         }
-                         post_table.push("```".to_string());
-                     }
-                 }
-             }
-
-             // Show rename/move details
-             if matches!(
-                 change.change_type,
-                 ChangeType::Renamed | ChangeType::Moved
-             ) {
-                 if let Some(ref old_path) = change.old_file_path {
-                     post_table.push(String::new());
-                     post_table.push(format!("> from {old_path}"));
-                 }
-             }
-         }
-
-         lines.extend(post_table);
-         lines.push(String::new());
-     }
-
-     // Summary
-     let mut parts: Vec<String> = Vec::new();
-     if result.added_count > 0 {
-         parts.push(format!("{} added", result.added_count));
-     }
-     if result.modified_count > 0 {
-         parts.push(format!("{} modified", result.modified_count));
-     }
-     if result.deleted_count > 0 {
-         parts.push(format!("{} deleted", result.deleted_count));
-     }
-     if result.moved_count > 0 {
-         parts.push(format!("{} moved", result.moved_count));
-     }
-     if result.renamed_count > 0 {
-         parts.push(format!("{} renamed", result.renamed_count));
-     }
-
-     let files_label = if result.file_count == 1 {
-         "file"
-     } else {
-         "files"
-     };
-
-     lines.push(format!(
-         "**Summary:** {} across {} {files_label}",
-         parts.join(", "),
-         result.file_count,
-     ));
-
-     lines.join("\n")
- }
+ pub fn format_markdown(result: &DiffResult, verbose: bool) -> String {
+     if result.changes.is_empty() {
+         return "No semantic changes detected.".to_string();
+     }
+
+     let mut lines: Vec<String> = Vec::new();
+
+     // Group changes by file (BTreeMap for sorted output)
+     let mut by_file: BTreeMap<&str, Vec<usize>> = BTreeMap::new();
+     for (i, change) in result.changes.iter().enumerate() {
+         by_file.entry(&change.file_path).or_default().push(i);
+     }
+
+     for (file_path, indices) in &by_file {
+         lines.push(format!("### {file_path}"));
+         lines.push(String::new());
+         lines.push("| Status | Type | Name |".to_string());
+         lines.push("|--------|------|------|".to_string());
+
+         let mut post_table: Vec<String> = Vec::new();
+
+         for &idx in indices {
+             let change = &result.changes[idx];
+             let status = match change.change_type {
+                 ChangeType::Added => "+",
+                 ChangeType::Deleted => "-",
+                 ChangeType::Modified => {
+                     if change.structural_change == Some(false) {
+                         "~"
+                     } else {
+                         "Δ"
+                     }
+                 }
+                 ChangeType::Moved => "→",
+                 ChangeType::Renamed => "↻",
+             };
+
+             let name_display = if let Some(ref old_name) = change.old_entity_name {
+                 format!("{old_name} -> {}", change.entity_name)
+             } else {
+                 change.entity_name.clone()
+             };
+             lines.push(format!(
+                 "| {} | {} | {} |",
+                 status, change.entity_type, name_display
+             ));
+
+             // Show content diff
+             if verbose {
+                 match change.change_type {
+                     ChangeType::Added => {
+                         if let Some(ref content) = change.after_content {
+                             post_table.push(String::new());
+                             post_table.push(format!("**`{}`**", change.entity_name));
+                             post_table.push("```diff".to_string());
+                             for line in content.lines() {
+                                 post_table.push(format!("+ {line}"));
+                             }
+                             post_table.push("```".to_string());
+                         }
+                     }
+                     ChangeType::Deleted => {
+                         if let Some(ref content) = change.before_content {
+                             post_table.push(String::new());
+                             post_table.push(format!("**`{}`**", change.entity_name));
+                             post_table.push("```diff".to_string());
+                             for line in content.lines() {
+                                 post_table.push(format!("- {line}"));
+                             }
+                             post_table.push("```".to_string());
+                         }
+                     }
+                     ChangeType::Modified => {
+                         if let (Some(before), Some(after)) =
+                             (&change.before_content, &change.after_content)
+                         {
+                             post_table.push(String::new());
+                             post_table.push(format!("**`{}`**", change.entity_name));
+                             post_table.push("```diff".to_string());
+                             let diff = TextDiff::from_lines(before.as_str(), after.as_str());
+                             for hunk in diff.unified_diff().context_radius(2).iter_hunks() {
+                                 post_table.push(hunk.header().to_string());
+                                 for op in hunk.ops() {
+                                     let mut deletes: Vec<String> = Vec::new();
+                                     let mut inserts: Vec<String> = Vec::new();
+
+                                     for diff_change in diff.iter_changes(op) {
+                                         let line = diff_change.value().trim_end_matches('\n');
+                                         match diff_change.tag() {
+                                             ChangeTag::Delete => deletes.push(line.to_string()),
+                                             ChangeTag::Insert => inserts.push(line.to_string()),
+                                             ChangeTag::Equal => post_table.push(format!("  {line}")),
+                                         }
+                                     }
+
+                                     let paired = deletes.len().min(inserts.len());
+                                     for i in 0..paired {
+                                         post_table.push(format!("- {}", deletes[i]));
+                                         post_table.push(format!("+ {}", inserts[i]));
+                                     }
+                                     for d in &deletes[paired..] {
+                                         post_table.push(format!("- {d}"));
+                                     }
+                                     for i in &inserts[paired..] {
+                                         post_table.push(format!("+ {i}"));
+                                     }
+                                 }
+                             }
+                             post_table.push("```".to_string());
+                         }
+                     }
+                     _ => {}
+                 }
+             } else if change.change_type == ChangeType::Modified {
+                 if let (Some(before), Some(after)) =
+                     (&change.before_content, &change.after_content)
+                 {
+                     let before_lines: Vec<&str> = before.lines().collect();
+                     let after_lines: Vec<&str> = after.lines().collect();
+
+                     if before_lines.len() <= 3 && after_lines.len() <= 3 {
+                         post_table.push(String::new());
+                         post_table.push(format!("**`{}`**", change.entity_name));
+                         post_table.push("```diff".to_string());
+                         for line in &before_lines {
+                             post_table.push(format!("- {}", line.trim()));
+                         }
+                         for line in &after_lines {
+                             post_table.push(format!("+ {}", line.trim()));
+                         }
+                         post_table.push("```".to_string());
+                     }
+                 }
+             }
+
+             // Show rename/move details
+             if matches!(
+                 change.change_type,
+                 ChangeType::Renamed | ChangeType::Moved
+             ) {
+                 if let Some(ref old_path) = change.old_file_path {
+                     post_table.push(String::new());
+                     post_table.push(format!("> from {old_path}"));
+                 }
+             }
+         }
+
+         lines.extend(post_table);
+         lines.push(String::new());
+     }
+
+     // Summary
+     let mut parts: Vec<String> = Vec::new();
+     if result.added_count > 0 {
+         parts.push(format!("{} added", result.added_count));
+     }
+     if result.modified_count > 0 {
+         parts.push(format!("{} modified", result.modified_count));
+     }
+     if result.deleted_count > 0 {
+         parts.push(format!("{} deleted", result.deleted_count));
+     }
+     if result.moved_count > 0 {
+         parts.push(format!("{} moved", result.moved_count));
+     }
+     if result.renamed_count > 0 {
+         parts.push(format!("{} renamed", result.renamed_count));
+     }
+
+     let files_label = if result.file_count == 1 {
+         "file"
+     } else {
+         "files"
+     };
+
+     lines.push(format!(
+         "**Summary:** {} across {} {files_label}",
+         parts.join(", "),
+         result.file_count,
+     ));
+
+     lines.join("\n")
+ }

Summary: 1 modified across 1 file

After

crates/sem-cli/src/formatters/markdown.rs

Status Type Name
Δ function format_markdown

format_markdown

@@ -78,9 +78,32 @@
                              post_table.push(format!("**`{}`**", change.entity_name));
                              post_table.push("```diff".to_string());
-                             for line in before.lines() {
+                             let diff = TextDiff::from_lines(before.as_str(), after.as_str());
-                                 post_table.push(format!("- {line}"));
+                             for hunk in diff.unified_diff().context_radius(2).iter_hunks() {
-                             }
+                                 post_table.push(hunk.header().to_string());
-                             for line in after.lines() {
+                                 for op in hunk.ops() {
-                                 post_table.push(format!("+ {line}"));
+                                     let mut deletes: Vec<String> = Vec::new();
+                                     let mut inserts: Vec<String> = Vec::new();
+
+                                     for diff_change in diff.iter_changes(op) {
+                                         let line = diff_change.value().trim_end_matches('\n');
+                                         match diff_change.tag() {
+                                             ChangeTag::Delete => deletes.push(line.to_string()),
+                                             ChangeTag::Insert => inserts.push(line.to_string()),
+                                             ChangeTag::Equal => post_table.push(format!("  {line}")),
+                                         }
+                                     }
+
+                                     let paired = deletes.len().min(inserts.len());
+                                     for i in 0..paired {
+                                         post_table.push(format!("- {}", deletes[i]));
+                                         post_table.push(format!("+ {}", inserts[i]));
+                                     }
+                                     for d in &deletes[paired..] {
+                                         post_table.push(format!("- {d}"));
+                                     }
+                                     for i in &inserts[paired..] {
+                                         post_table.push(format!("+ {i}"));
+                                     }
+                                 }
                              }
                              post_table.push("```".to_string());

Summary: 1 modified across 1 file

@rs545837
Copy link
Copy Markdown
Member

rs545837 commented Apr 9, 2026

Thanks for opening the PR, yeah I didn't get time to design a solid case for markdown files. I will review this PR soon.

@Benjas333
Copy link
Copy Markdown
Contributor Author

Benjas333 commented Apr 11, 2026

Sorry for the large commit history. Didn't know I was doing git rebase wrong this whole time. My bad.

@Benjas333 Benjas333 force-pushed the fix-markdown-verbose branch from 8d17178 to b6f7b5f Compare April 11, 2026 01:40
@Benjas333
Copy link
Copy Markdown
Contributor Author

Now it's fixed. Sorry for the inconvenience.

@rs545837 rs545837 merged commit db9455f into Ataraxy-Labs:main Apr 11, 2026
1 check passed
@Benjas333 Benjas333 deleted the fix-markdown-verbose branch April 11, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Diff too large on markdown format

2 participants