Unified Agent + reusable Go agent core.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(clifmt): fill diff line background to full terminal width

Replace \x1b[K (erase-to-EOL) with explicit space padding based on
terminal width detection. BCE (Background Color Erase) is unreliable
in many terminals when combined with truecolor SGR backgrounds and
CJK wide characters, leaving default-bg black gaps after the last
visible character.

Also expand tabs to spaces before diffing, since tab jumps to the
next tab stop often render with default background instead of the
active SGR background.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

authored by

Teteuya
Claude Sonnet 4.6
and committed by
Lyric Wai
3d0de2ca bcc10b25

+70 -4
+70 -4
internal/clifmt/diff.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 + "os" 5 6 "path/filepath" 6 7 "regexp" 7 8 "strings" 8 9 10 + "github.com/mattn/go-runewidth" 9 11 "github.com/sergi/go-diff/diffmatchpatch" 12 + "golang.org/x/term" 10 13 ) 11 14 12 15 // ansiBgRe matches ANSI escape sequences that set or reset background colors. 13 16 var ansiBgRe = regexp.MustCompile("\x1b\\[(?:4[0-8]|10[0-7]|49)(?:;[^m]*)?m") 17 + 18 + // reapplyBgBeforeWideChars re-emits the bg ANSI sequence immediately before 19 + // every double-width (CJK) rune in text. Some terminals fail to fill the 20 + // right half-cell of a wide glyph with the active SGR background, leaving a 21 + // stray default-bg block that looks like a black rectangle inside CJK runs. 22 + // Forcing a fresh bg SGR right before each wide rune nudges those terminals 23 + // into propagating the background across the full glyph. 24 + func reapplyBgBeforeWideChars(text, bg string) string { 25 + if bg == "" { 26 + return text 27 + } 28 + var b strings.Builder 29 + b.Grow(len(text)) 30 + inAnsi := false 31 + for _, r := range text { 32 + if inAnsi { 33 + b.WriteRune(r) 34 + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { 35 + inAnsi = false 36 + } 37 + continue 38 + } 39 + if r == '\x1b' { 40 + inAnsi = true 41 + b.WriteRune(r) 42 + continue 43 + } 44 + if runewidth.RuneWidth(r) >= 2 { 45 + b.WriteString(bg) 46 + } 47 + b.WriteRune(r) 48 + } 49 + return b.String() 50 + } 14 51 15 52 // DiffLine represents a single line in a unified diff output. 16 53 type diffLine struct { ··· 199 236 // background color, applies syntax highlighting to code, and folds long stretches of 200 237 // unchanged context. 201 238 func RenderDiff(path, oldContent, newContent string) string { 239 + // Expand tabs to spaces so background colors can fill indentation cleanly. 240 + // Tab characters render as a "jump to next tab stop" in terminals, and the 241 + // jumped-over cells often get filled with the terminal default background 242 + // (black) rather than the active SGR background — producing dark blocks at 243 + // the start of indented diff/context lines. 244 + oldContent = strings.ReplaceAll(oldContent, "\t", " ") 245 + newContent = strings.ReplaceAll(newContent, "\t", " ") 246 + 202 247 lines := lineDiff(oldContent, newContent) 203 248 if len(lines) == 0 { 204 249 return "" ··· 246 291 } 247 292 } 248 293 294 + termWidth := 0 295 + if term.IsTerminal(int(os.Stdout.Fd())) { 296 + if w, _, err := term.GetSize(int(os.Stdout.Fd())); err == nil && w > 0 { 297 + termWidth = w 298 + } 299 + } 300 + 249 301 var b strings.Builder 250 302 251 303 gray := "" ··· 301 353 b.WriteString(fmt.Sprintf("%s%*d%s - ", gray, gutterWidth, lineNum, fg)) 302 354 safeText := ansiBgRe.ReplaceAllString(text, "") 303 355 safeText = strings.ReplaceAll(safeText, "\x1b[0m", "\x1b[39m"+bg+fg) 356 + safeText = reapplyBgBeforeWideChars(safeText, bg) 304 357 b.WriteString(safeText) 305 - b.WriteString(bg) 306 - b.WriteString("\x1b[K\x1b[0m") 358 + if termWidth > 0 { 359 + used := gutterWidth + 3 + visibleWidth(safeText) 360 + if pad := termWidth - used; pad > 0 { 361 + b.WriteString(bg) 362 + b.WriteString(strings.Repeat(" ", pad)) 363 + } 364 + } 365 + b.WriteString("\x1b[0m") 307 366 } else { 308 367 b.WriteString(fmt.Sprintf("%*d - %s", gutterWidth, lineNum, text)) 309 368 } ··· 315 374 b.WriteString(fmt.Sprintf("%s%*d%s + ", gray, gutterWidth, lineNum, fg)) 316 375 safeText := ansiBgRe.ReplaceAllString(text, "") 317 376 safeText = strings.ReplaceAll(safeText, "\x1b[0m", "\x1b[39m"+bg+fg) 377 + safeText = reapplyBgBeforeWideChars(safeText, bg) 318 378 b.WriteString(safeText) 319 - b.WriteString(bg) 320 - b.WriteString("\x1b[K\x1b[0m") 379 + if termWidth > 0 { 380 + used := gutterWidth + 3 + visibleWidth(safeText) 381 + if pad := termWidth - used; pad > 0 { 382 + b.WriteString(bg) 383 + b.WriteString(strings.Repeat(" ", pad)) 384 + } 385 + } 386 + b.WriteString("\x1b[0m") 321 387 } else { 322 388 b.WriteString(fmt.Sprintf("%*d + %s", gutterWidth, lineNum, text)) 323 389 }