From 687ffd94cf5d21289b390916c018f3e8d17021e6 Mon Sep 17 00:00:00 2001 From: Patrick Dewey
Date: Wed, 1 Apr 2026 09:38:32 -0400 Subject: [PATCH] feat: don't truncate snapshot lines in tui view --- .gitignore | 2 - LICENSE | 2 +- cmd/shutter/go.sum | 1 - cmd/shutter/main.go | 4 +- go.work | 6 +++ go.work.sum | 13 ++++++ internal/files/files_test.go | 4 +- internal/pretty/boxes.go | 84 +++++++++++++++++++++++++++++------- 8 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 go.work create mode 100644 go.work.sum diff --git a/.gitignore b/.gitignore index e7031de..5bd2ddd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,6 @@ *.dylib *.test *.out -go.work -go.work.sum .env cmd/tui/shutter cmd/shutter/shutter diff --git a/LICENSE b/LICENSE index e3266fa..d1d1f75 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Patrick Dewey +Copyright (c) 2025 - 2026 Patrick Dewey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/cmd/shutter/go.sum b/cmd/shutter/go.sum index ffbfa3f..630e516 100644 --- a/cmd/shutter/go.sum +++ b/cmd/shutter/go.sum @@ -33,7 +33,6 @@ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIf github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/ptdewey/shutter v0.1.4 h1:tMTNMTxCpA1F0REyi+taztoHVe9EpB5sSKhaIBzYu1c= -github.com/ptdewey/shutter v0.1.4/go.mod h1:teeIXF4LdgsE9E4kjHk9nGzDxl2cjdbVb1qbdzAHSR4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= diff --git a/cmd/shutter/main.go b/cmd/shutter/main.go index c1da124..e3650de 100644 --- a/cmd/shutter/main.go +++ b/cmd/shutter/main.go @@ -258,10 +258,10 @@ func (m *model) updateViewportContent() { // Show diff or new snapshot if m.accepted != nil && m.diffLines != nil { - b.WriteString(pretty.DiffSnapshotBox(m.accepted, m.newSnap, m.diffLines)) + b.WriteString(pretty.DiffSnapshotBox(m.accepted, m.newSnap, m.diffLines, m.width)) } else { if m.newSnap != nil { - b.WriteString(pretty.NewSnapshotBox(m.newSnap)) + b.WriteString(pretty.NewSnapshotBox(m.newSnap, m.width)) } } diff --git a/go.work b/go.work new file mode 100644 index 0000000..cebb485 --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.25.2 + +use ( + . + ./cmd/shutter +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..145e25c --- /dev/null +++ b/go.work.sum @@ -0,0 +1,13 @@ +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/ptdewey/shutter v0.1.4/go.mod h1:teeIXF4LdgsE9E4kjHk9nGzDxl2cjdbVb1qbdzAHSR4= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/internal/files/files_test.go b/internal/files/files_test.go index 51cf739..9c68130 100644 --- a/internal/files/files_test.go +++ b/internal/files/files_test.go @@ -256,12 +256,12 @@ func TestRecursiveSnapshots(t *testing.T) { if err != nil { t.Fatalf("ListNewSnapshots failed: %v", err) } - + t.Logf("Found %d snapshots", len(snapshots)) for _, snap := range snapshots { t.Logf(" - Title: %s, Path: %s", snap.Title, snap.Path) } - + // Just verify it doesn't error - we can't make assumptions about which // snapshots exist since this depends on the test environment if err != nil { diff --git a/internal/pretty/boxes.go b/internal/pretty/boxes.go index 1786e1d..ce57a0c 100644 --- a/internal/pretty/boxes.go +++ b/internal/pretty/boxes.go @@ -8,8 +8,12 @@ import ( "github.com/ptdewey/shutter/internal/files" ) -func NewSnapshotBox(snap *files.Snapshot) string { - return newSnapshotBoxInternal(snap) +func NewSnapshotBox(snap *files.Snapshot, width ...int) string { + w := TerminalWidth() + if len(width) > 0 && width[0] > 0 { + w = width[0] + } + return newSnapshotBoxInternal(snap, w) } // calculateLineNumWidth returns the width needed to display line numbers @@ -31,8 +35,11 @@ func formatColoredLine(line string, kind diff.DiffKind) string { } } -func DiffSnapshotBox(old, newSnapshot *files.Snapshot, diffLines []diff.DiffLine) string { +func DiffSnapshotBox(old, newSnapshot *files.Snapshot, diffLines []diff.DiffLine, widthOpt ...int) string { width := TerminalWidth() + if len(widthOpt) > 0 && widthOpt[0] > 0 { + width = widthOpt[0] + } snapshotFileName := files.SnapshotFileName(newSnapshot.Title) + ".snap" var sb strings.Builder @@ -99,16 +106,40 @@ func DiffSnapshotBox(old, newSnapshot *files.Snapshot, diffLines []diff.DiffLine formatted = dl.Line } - // Adjust for actual display length considering ANSI codes + // Wrap long lines instead of truncating // Account for: 2 spaces padding + 2 line number columns + 2 spaces between + prefix + space maxContentWidth := width - (lineNumWidth * 2) - 8 - if len(dl.Line) > maxContentWidth { - truncated := dl.Line[:maxContentWidth-3] + "..." - formatted = formatColoredLine(truncated, dl.Kind) + if maxContentWidth < 20 { + maxContentWidth = 20 } - display := fmt.Sprintf("%s %s %s %s", leftNum, rightNum, prefix, formatted) - sb.WriteString(fmt.Sprintf(" %s\n", display)) + if len(dl.Line) > maxContentWidth { + // Emit wrapped chunks with proper gutter alignment + line := dl.Line + first := true + for len(line) > 0 { + chunk := line + if len(chunk) > maxContentWidth { + chunk = line[:maxContentWidth] + line = line[maxContentWidth:] + } else { + line = "" + } + coloredChunk := formatColoredLine(chunk, dl.Kind) + if first { + display := fmt.Sprintf("%s %s %s %s", leftNum, rightNum, prefix, coloredChunk) + sb.WriteString(fmt.Sprintf(" %s\n", display)) + first = false + } else { + pad := strings.Repeat(" ", lineNumWidth) + display := fmt.Sprintf("%s %s %s %s", pad, pad, "│", coloredChunk) + sb.WriteString(fmt.Sprintf(" %s\n", display)) + } + } + } else { + display := fmt.Sprintf("%s %s %s %s", leftNum, rightNum, prefix, formatted) + sb.WriteString(fmt.Sprintf(" %s\n", display)) + } } // Bottom bar with corner (account for both line number columns) @@ -119,8 +150,7 @@ func DiffSnapshotBox(old, newSnapshot *files.Snapshot, diffLines []diff.DiffLine return sb.String() } -func newSnapshotBoxInternal(snap *files.Snapshot) string { - width := TerminalWidth() +func newSnapshotBoxInternal(snap *files.Snapshot, width int) string { var sb strings.Builder sb.WriteString("─── " + "New Snapshot " + strings.Repeat("─", width-15) + "\n\n") @@ -148,12 +178,36 @@ func newSnapshotBoxInternal(snap *files.Snapshot) string { lineNum := fmt.Sprintf("%*d", lineNumWidth, i+1) prefix := fmt.Sprintf("%s %s", Green(lineNum), Green("+")) - if len(line) > width-len(prefix)-4 { - line = line[:width-len(prefix)-7] + "..." + maxContentWidth := width - lineNumWidth - 6 + if maxContentWidth < 20 { + maxContentWidth = 20 } - display := fmt.Sprintf("%s %s", prefix, Green(line)) - sb.WriteString(fmt.Sprintf(" %s\n", display)) + if len(line) > maxContentWidth { + remaining := line + first := true + for len(remaining) > 0 { + chunk := remaining + if len(chunk) > maxContentWidth { + chunk = remaining[:maxContentWidth] + remaining = remaining[maxContentWidth:] + } else { + remaining = "" + } + if first { + display := fmt.Sprintf("%s %s", prefix, Green(chunk)) + sb.WriteString(fmt.Sprintf(" %s\n", display)) + first = false + } else { + pad := strings.Repeat(" ", lineNumWidth) + display := fmt.Sprintf("%s %s %s", pad, "│", Green(chunk)) + sb.WriteString(fmt.Sprintf(" %s\n", display)) + } + } + } else { + display := fmt.Sprintf("%s %s", prefix, Green(line)) + sb.WriteString(fmt.Sprintf(" %s\n", display)) + } } bottomBar := strings.Repeat("─", lineNumWidth+3) + "┴" + -- 2.51.2