MUG currently outputs basic ASCII text. By adding Unicode box-drawing characters, tree symbols, and emojis, we can make output significantly more beautiful and readable—matching the elegance of tools like Jujutsu.
Branches:
* main
feature-x
hotfix-bug
Commit History:
abc1234 (HEAD -> main) Update docs
def5678 (feature-x) Add new feature
ghi9012 Fix critical bug
┌─ Branches ──────────────────────────────────────────
│
├─ ● main
│
├─ ○ feature-x
│
└─ ○ hotfix-bug
┌─ Commit History ─────────────────────────────────────
│
├─ ◆ abc1234 (HEAD -> main) Update docs
│ │
│ └─ def5678 (feature-x) Add new feature
│ │
│ └─ ghi9012 Fix critical bug
│
└─ ⚓ (root)
abc1234 Update docs
def5678 Add new feature
◆ abc1234 (HEAD -> main) Update docs
│
◆ def5678 (feature-x) Add new feature
│
◆ ghi9012 Fix critical bug
~
With merge/branch:
@ xyz9999 (working) (no message)
│
◆ abc1234 (main) Update docs
├─╮
│ ◆ def5678 (feature-x) Add new feature
│ │
◆ │ ghi9012 (hotfix-bug) Fix critical bug
├─╯
◆ aaa0000 (root) Initial commit
On branch main
Changes not staged for commit:
modified: src/main.rs
deleted: docs/old.md
Untracked files:
test.rs
┌─ Status ──────────────────────────────────────────────────┐
│ 📍 On branch main │
├───────────────────────────────────────────────────────────┤
│ 📝 Changes not staged for commit: │
│ ✏️ modified src/main.rs │
│ 🗑 deleted docs/old.md │
│ │
│ 📦 Untracked files: │
│ ❓ test.rs │
└───────────────────────────────────────────────────────────┘
●●● 2 modified, 1 deleted, 1 untracked
main (tracked)
* feature-work
development
┌─ Branches ──────────────────────────────────────────┐
│ │
│ ◆ ● main (tracked) │
│ │ │
│ └─ ○ feature-work (current) │
│ │ │
│ └─ ○ development (local) │
│ │
└─────────────────────────────────────────────────────┘
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
fn main() {
+ println!("Hello!");
let x = 5;
┌─ Diff: src/main.rs ──────────────────────────────────┐
│ │
│ fn main() { │
│ ➕ println!("Hello!"); │
│ let x = 5; │
│ │
└──────────────────────────────────────────────────────┘
<<<<<<<< HEAD
our changes
=======
their changes
>>>>>>>
┌─ Conflict in src/main.rs ────────────────┐
│ │
│ ⬅️ (current) ──────────────────────── │
│ our changes │
│ │
│ ➡️ (incoming) ─────────────────────── │
│ their changes │
│ │
└──────────────────────────────────────────┘
Packing objects... [================> ] 50%
┌─ Packing Objects ────────────────────────────┐
│ ███████████░░░░░░░░░░░░░░░░░░ 50% (1/2 GB) │
│ ⏱ ETA: 2m 30s │
└──────────────────────────────────────────────┘
error: file not found
warning: ambiguous reference
❌ error: file not found
→ src/nonexistent.rs
⚠️ warning: ambiguous reference
→ multiple branches match 'feature'
Use: mug checkout feature-1
◆ Regular commit
◉ Main commit (current branch)
@ Working copy commit
~ Virtual commit (root)
○ Empty commit
✓ Completed
✗ Failed
⚠️ Warning/Conflict
├─ Branch from parent
│ Line continuation
├─╮ Merge point (multiple parents)
│ ╭┴─ Branch merge
└─╯ Merge end
◆ Commit on branch
→ Points to
⚓ Root/anchor
● Current (active)
○ Inactive branch
✏️ Modified
➕ Added
🗑 Deleted
🏷 Renamed
↔️ Moved
🔗 Copied
📍 Location/branch
📝 Changes
📦 Staging area
❓ Untracked
✓ Staged
⚡ Unstaged
🔒 Committed
⬅️ Incoming/their
➡️ Outgoing/ours
↑ Push
↓ Pull/Fetch
🔄 Rebase
📌 Tag
🌿 Branch
💾 Save/Stash
⊗ Removed/deleted
✈️ Remote
🗂 Directory
📄 File
🔑 Key
🔐 Encrypted
⊖ Conflict
pub mod formatter {
pub struct UnicodeFormatter {
pub use_colors: bool,
pub use_unicode: bool,
pub width: usize, // Terminal width
}
impl UnicodeFormatter {
pub fn format_commit_graph(&self, commits: &[Commit]) -> String
pub fn format_status(&self, status: &Status) -> String
pub fn format_branch_list(&self, branches: &[Branch]) -> String
pub fn format_diff(&self, diff: &Diff) -> String
pub fn format_error(&self, error: &str, context: &str) -> String
pub fn format_progress_bar(&self, current: u64, total: u64) -> String
pub fn format_table(&self, headers: &[&str], rows: Vec<Vec<&str>>) -> String
}
// Helper functions for common elements
pub fn box_drawing(title: &str, content: &str, width: usize) -> String
pub fn tree_branch(indent: usize) -> &'static str
pub fn commit_symbol(is_current: bool, is_merge: bool) -> &'static str
pub fn file_status_icon(kind: FileChangeKind) -> &'static str
pub fn colorize(text: &str, color: Color) -> String
}[dependencies]
# Already exists:
ratatui = "0.27" # TUI library with Unicode support
crossterm = "0.28" # Terminal control
# New additions:
unicode-width = "0.1" # Measure Unicode string widths
textwrap = "0.16" # Word wrapping
colored = "2.1" # Color output (simple)
# OR
owo-colors = "3.5" # More elegant coloringAdd to .mug/config:
[ui]
use_unicode = true # Enable Unicode characters
use_colors = true # Enable colors
use_emojis = false # Use emoji (optional, more playful)
auto_detect = true # Auto-detect terminal capabilities
terminal_width = 0 # 0 = auto-detect
[colors]
branch_current = "green"
branch_inactive = "gray"
added = "green"
removed = "red"
modified = "yellow"
commit_hash = "cyan"fn cmd_log(repo: &Repository, args: LogArgs) -> Result<()> {
let commits = repo.log()?;
let formatter = UnicodeFormatter::new(&config);
let output = formatter.format_commit_graph(&commits);
println!("{}", output);
Ok(())
}fn cmd_status(repo: &Repository) -> Result<()> {
let status = repo.status()?;
let formatter = UnicodeFormatter::new(&config);
let output = formatter.format_status(&status);
println!("{}", output);
Ok(())
}fn show_progress(current: u64, total: u64, label: &str) {
let formatter = UnicodeFormatter::new(&config);
let bar = formatter.format_progress_bar(current, total);
eprint!("\r{}: {}", label, bar);
if current >= total {
eprintln!("\n✓ Complete!");
}
}Primary Colors:
- Green (#00FF00) → Success, current branch, added files
- Red (#FF0000) → Errors, deleted files, conflicts
- Yellow (#FFFF00) → Warnings, modified files
- Cyan (#00FFFF) → Hashes, links, metadata
- Blue (#0000FF) → Branches, tags, headers
- Gray (#808080) → Muted, old, inactive
Backgrounds:
- Dark theme: Dark gray background (#1E1E1E)
- Light theme: Light gray background (#F0F0F0)
pub enum LogColor {
CommitHash, // Cyan
BranchName, // Green
AuthorName, // Blue
Timestamp, // Gray
Message, // White
Tag, // Yellow
}
pub enum StatusColor {
Added, // Green (➕)
Deleted, // Red (🗑)
Modified, // Yellow (✏️)
Untracked, // Gray (❓)
Header, // Blue
}pub fn detect_capabilities() -> TerminalCapabilities {
let supports_unicode =
env::var("LANG").ok().map(|l| l.contains("UTF")).unwrap_or(false) ||
env::var("LC_ALL").ok().map(|l| l.contains("UTF")).unwrap_or(false) ||
cfg!(windows); // Windows 10+ supports Unicode
let supports_colors =
atty::isnt(atty::Stream::Stdout).not() &&
env::var("NO_COLOR").is_err();
let terminal_width =
term_size::dimensions().map(|(w, _)| w).unwrap_or(80);
TerminalCapabilities {
supports_unicode,
supports_colors,
supports_hyperlinks: env::var("TERM_PROGRAM").is_ok(),
width: terminal_width,
}
}If terminal doesn't support Unicode:
Unicode: ◆ → [*]
Unicode: ├─ → |--
Unicode: ✓ → OK
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_commit_graph_formatting() {
let formatter = UnicodeFormatter::new_test();
let commits = vec![/* test data */];
let output = formatter.format_commit_graph(&commits);
assert!(output.contains("◆"));
assert!(output.contains("│"));
}
#[test]
fn test_progress_bar_formatting() {
let formatter = UnicodeFormatter::new_test();
let bar = formatter.format_progress_bar(50, 100);
assert!(bar.contains("███"));
assert!(bar.contains("50%"));
}
#[test]
fn test_unicode_width_calculation() {
let formatter = UnicodeFormatter::new_test();
assert_eq!(formatter.measure_width("hello"), 5);
assert_eq!(formatter.measure_width("café"), 4); // é is 1 width
}
#[test]
fn test_fallback_ascii_mode() {
let mut formatter = UnicodeFormatter::new_test();
formatter.use_unicode = false;
let output = formatter.format_status(&status);
assert!(!output.contains("◆"));
assert!(output.contains("[*]")); // ASCII fallback
}
}- Create
src/ui/formatter.rsmodule - Add Unicode character constants
- Implement basic formatting functions
- Terminal capability detection
- Enhance
mug logwith Unicode graph - Test with various commit patterns (linear, merge, branching)
- Color support
- Enhance
mug statusoutput - Enhance
mug branchesdisplay - Add file operation icons
- Enhance
mug diffdisplay - Conflict visualization
- Syntax highlighting (optional)
- Progress bars
- Error/warning formatting
- Comprehensive testing
- Documentation
Before:
* main
feature-1
feature-2
develop
After:
┌─ Branches ────────────────────────────┐
│ │
│ 🌿 ● main (current) │
│ │
│ 🌿 ○ feature-1 │
│ │
│ 🌿 ○ feature-2 │
│ │ │
│ └─ 🌿 ○ develop (stale) │
│ │
└───────────────────────────────────────┘
Before:
abc1234 (HEAD -> main, origin/main) Update docs
def5678 (feature-x) Add feature
ghi9012 Fix bug
After:
◆ abc1234 (HEAD -> main) ▍▍▍▍▍▍▍▍ Update documentation
│ (origin/main)
│
◉ def5678 (feature-x) ▍▍▍▍▍▍ Add new feature
│
◆ ghi9012 ▍▍▍ Fix critical bug
│
~ 000000 (root)
Before:
* merge-commit
|\
| * feature-branch
|/
* main-branch
After:
@ abc1234 (working) Merge feature
├─╮
│ ◆ def5678 (feature) Add feature
│ │
◆ │ ghi9012 (main) Update code
├─╯
◆ jkl3456 Initial commit
~ 000000 (root)
- Visual Clarity: Graph structure is immediately visible
- Less Ambiguity: Clear symbols for different states
- Professionalism: Looks polished like modern tools
- Better UX: Status/errors are easier to scan
- Learning: Visual hierarchy helps users understand concepts
- Accessibility: Works in limited-color terminals with ASCII fallback
- Syntax Highlighting: Color code by language
- Interactive TUI: Use ratatui for interactive selection
- Hyperlinks: OSC 8 hyperlinks in compatible terminals
- Animations: Subtle animations for long operations
- Themes: User-defined color schemes
- Accessibility: High-contrast mode for vision impairment