From 5987382f9a00ccc9098e0a294187be864a0f1e05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:23:48 +0000 Subject: [PATCH 1/5] Initial plan From e4a3210e5689a143055d4bcffcb9299a600f4438 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:28:18 +0000 Subject: [PATCH 2/5] Fix color overflow issue in split-pane layout Co-authored-by: ShepAlderson <1037047+ShepAlderson@users.noreply.github.com> --- internal/tui/model.go | 67 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/internal/tui/model.go b/internal/tui/model.go index 1b66acd..a82a3cd 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -7,6 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" + "github.com/charmbracelet/x/ansi" "github.com/shepbook/github-issues-tui/internal/sync" "github.com/shepbook/github-issues-tui/internal/theme" ) @@ -633,11 +634,14 @@ func (m Model) renderListPanel(width, height int) string { } line := fmt.Sprintf("%s %s", cursor, m.renderIssue(issue)) - // Truncate to width + // Truncate to width before applying style (plain text, no ANSI yet) if len(line) > width { line = line[:width] } - b.WriteString(style.Render(line)) + // Apply style and ensure it fits width with padding + styledLine := style.Render(line) + styledLine = padLineWithANSI(styledLine, width) + b.WriteString(styledLine) b.WriteString("\n") linesRendered++ } @@ -722,11 +726,9 @@ func (m Model) renderDetailPanel(width, height int) string { scrolledLines := lines[startLine:endLine] - // Truncate each line to width + // Properly truncate and pad each line to width (accounting for ANSI codes) for i, line := range scrolledLines { - if len(line) > width { - scrolledLines[i] = line[:width] - } + scrolledLines[i] = padLineWithANSI(line, width) } return strings.Join(scrolledLines, "\n") @@ -955,6 +957,59 @@ func truncate(s string, maxLen int) string { return s[:maxLen-3] + "..." } +// truncateLineWithANSI properly truncates a string containing ANSI codes to a visual width +func truncateLineWithANSI(s string, width int) string { + visualWidth := ansi.StringWidth(s) + if visualWidth <= width { + return s + } + + // Need to truncate - iterate through the string to find where to cut + currentWidth := 0 + var result strings.Builder + inEscape := false + escapeSeq := strings.Builder{} + + for _, r := range s { + if r == '\x1b' { + inEscape = true + escapeSeq.Reset() + escapeSeq.WriteRune(r) + continue + } + + if inEscape { + escapeSeq.WriteRune(r) + // End of escape sequence + if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') { + result.WriteString(escapeSeq.String()) + inEscape = false + } + continue + } + + // Regular character - check if we have room + if currentWidth >= width { + break + } + + result.WriteRune(r) + currentWidth++ + } + + return result.String() +} + +// padLineWithANSI pads a string containing ANSI codes to a visual width +func padLineWithANSI(s string, width int) string { + visualWidth := ansi.StringWidth(s) + if visualWidth >= width { + return truncateLineWithANSI(s, width) + } + padding := width - visualWidth + return s + strings.Repeat(" ", padding) +} + func min(a, b int) int { if a < b { return a From bdc5ffe87b2c1bfe540050434e4457d72088cdb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:29:44 +0000 Subject: [PATCH 3/5] Improve Unicode handling in truncation function Co-authored-by: ShepAlderson <1037047+ShepAlderson@users.noreply.github.com> --- internal/tui/model.go | 40 +--------------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/internal/tui/model.go b/internal/tui/model.go index a82a3cd..ef3c40d 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -959,45 +959,7 @@ func truncate(s string, maxLen int) string { // truncateLineWithANSI properly truncates a string containing ANSI codes to a visual width func truncateLineWithANSI(s string, width int) string { - visualWidth := ansi.StringWidth(s) - if visualWidth <= width { - return s - } - - // Need to truncate - iterate through the string to find where to cut - currentWidth := 0 - var result strings.Builder - inEscape := false - escapeSeq := strings.Builder{} - - for _, r := range s { - if r == '\x1b' { - inEscape = true - escapeSeq.Reset() - escapeSeq.WriteRune(r) - continue - } - - if inEscape { - escapeSeq.WriteRune(r) - // End of escape sequence - if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') { - result.WriteString(escapeSeq.String()) - inEscape = false - } - continue - } - - // Regular character - check if we have room - if currentWidth >= width { - break - } - - result.WriteRune(r) - currentWidth++ - } - - return result.String() + return ansi.Truncate(s, width, "") } // padLineWithANSI pads a string containing ANSI codes to a visual width From f4d638b04307e9af9aa017c35754f5a723515d3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:30:45 +0000 Subject: [PATCH 4/5] Clean up code per review feedback Co-authored-by: ShepAlderson <1037047+ShepAlderson@users.noreply.github.com> --- internal/tui/model.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/internal/tui/model.go b/internal/tui/model.go index ef3c40d..2750bf3 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -634,10 +634,6 @@ func (m Model) renderListPanel(width, height int) string { } line := fmt.Sprintf("%s %s", cursor, m.renderIssue(issue)) - // Truncate to width before applying style (plain text, no ANSI yet) - if len(line) > width { - line = line[:width] - } // Apply style and ensure it fits width with padding styledLine := style.Render(line) styledLine = padLineWithANSI(styledLine, width) @@ -957,16 +953,11 @@ func truncate(s string, maxLen int) string { return s[:maxLen-3] + "..." } -// truncateLineWithANSI properly truncates a string containing ANSI codes to a visual width -func truncateLineWithANSI(s string, width int) string { - return ansi.Truncate(s, width, "") -} - // padLineWithANSI pads a string containing ANSI codes to a visual width func padLineWithANSI(s string, width int) string { visualWidth := ansi.StringWidth(s) if visualWidth >= width { - return truncateLineWithANSI(s, width) + return ansi.Truncate(s, width, "") } padding := width - visualWidth return s + strings.Repeat(" ", padding) From 2db8ddcb9cb02f41c9e995ffd674799f07d3a44d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:32:32 +0000 Subject: [PATCH 5/5] Complete code review Co-authored-by: ShepAlderson <1037047+ShepAlderson@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 776adf8..a89404f 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,12 @@ go 1.25.5 require ( github.com/charmbracelet/bubbletea v1.3.10 + github.com/charmbracelet/glamour v0.10.0 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 + github.com/charmbracelet/x/ansi v0.10.1 github.com/pelletier/go-toml/v2 v2.2.4 github.com/schollz/progressbar/v3 v3.19.0 + golang.org/x/term v0.31.0 modernc.org/sqlite v1.44.2 ) @@ -15,8 +18,6 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/glamour v0.10.0 // indirect - github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect github.com/charmbracelet/x/term v0.2.1 // indirect @@ -44,7 +45,6 @@ require ( golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.37.0 // indirect - golang.org/x/term v0.31.0 // indirect golang.org/x/text v0.24.0 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect diff --git a/go.sum b/go.sum index 28a5292..9d04d55 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,13 @@ +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= @@ -10,16 +16,14 @@ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4p github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= @@ -42,6 +46,8 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -98,12 +104,8 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=