aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-04-17 20:44:41 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-04-17 20:44:41 -0700
commitfdb09f1c9d9644b31f8aef77b3bb0a10bdd8b8d7 (patch)
tree13ca3c12f50794e51e23b921dff66e9251c8b4a8
parent944adeb2f9ca31c1a6cb7a8497555d97ea0e51d8 (diff)
downloadccp-fdb09f1c9d9644b31f8aef77b3bb0a10bdd8b8d7.tar.zst
Handle lines longer than terminal width
-rw-r--r--ccp.go40
-rw-r--r--internal/render/render.go57
2 files changed, 77 insertions, 20 deletions
diff --git a/ccp.go b/ccp.go
index a7082e4..94eac96 100644
--- a/ccp.go
+++ b/ccp.go
@@ -7,9 +7,11 @@ import (
"flag"
"fmt"
"os"
+ "path/filepath"
"strings"
"sync"
"time"
+ "unicode/utf8"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/lipgloss"
@@ -33,9 +35,10 @@ type measurement struct {
// progressUpdater implements the cp.Progress interface.
type progressUpdater struct {
mu sync.Mutex
- max int64 // Total bytes to copy
- current int64 // Current bytes copied
- copyingFile string // File currently being copied
+ max int64 // Total bytes to copy
+ current int64 // Current bytes copied
+ copyingFrom string // File currently being copied
+ copyingTo string
errs []error // Any errors encountered
}
@@ -51,11 +54,21 @@ func (pu *progressUpdater) Progress(n int64) {
pu.current += n
}
+func abbreviatePath(p string) string {
+ parts := strings.Split(p, string(filepath.Separator))
+ for i := 1; i < len(parts)-1; i++ {
+ part := parts[i]
+ _, n := utf8.DecodeRuneInString(part)
+ parts[i] = part[:n]
+ }
+ return strings.Join(parts, string(filepath.Separator))
+}
+
func (pu *progressUpdater) FileStart(from, to string) {
- s := from + " -> " + to
pu.mu.Lock()
defer pu.mu.Unlock()
- pu.copyingFile = s
+ pu.copyingFrom = from
+ pu.copyingTo = to
}
func (pu *progressUpdater) Error(err error) {
@@ -163,16 +176,27 @@ func run() error {
currentProgress.mu.Lock()
current := currentProgress.current
max := currentProgress.max
- copyingFile := currentProgress.copyingFile
+ copyingFrom := currentProgress.copyingFrom
+ copyingTo := currentProgress.copyingTo
errs := currentProgress.errs
currentProgress.mu.Unlock()
- renderer.Clear()
+ renderer.Clear(width)
+ copyingFile := ""
+ if copyingFrom != "" {
+ copyingFile = copyingFrom + " -> " + copyingTo
+ if len(copyingFile)+4 > width {
+ copyingFile = copyingFrom + " -> " + abbreviatePath(copyingTo)
+ if len(copyingFile)+4 > width {
+ copyingFile = abbreviatePath(copyingFrom) + " -> " + abbreviatePath(copyingTo)
+ }
+ }
+ }
progress := 0.
if max > 0 {
progress = float64(current) / float64(max)
}
- etaStr := "calculating..."
+ etaStr := "..."
if eta >= 0 {
etaStr = eta.Round(time.Second).String()
}
diff --git a/internal/render/render.go b/internal/render/render.go
index 877e2bd..18708f3 100644
--- a/internal/render/render.go
+++ b/internal/render/render.go
@@ -6,6 +6,7 @@ import (
"bytes"
"fmt"
"os"
+ "unicode/utf8"
)
// Renderer updates a terminal UI. Typical usage looks like
@@ -20,8 +21,10 @@ import (
// r.Flush()
// }
type Renderer struct {
- w bufio.Writer
- prevLines int
+ w bufio.Writer
+ prevLines int
+ width int
+ partialLineLen int
}
// New creates a new Renderer
@@ -32,12 +35,38 @@ func New() *Renderer {
}
// Clear clears the screen before rendering a new frame.
-func (r *Renderer) Clear() {
+func (r *Renderer) Clear(width int) {
+ r.width = width
if r.prevLines > 0 {
fmt.Fprintf(&r.w, "\033[%dA", r.prevLines)
}
- r.prevLines = 0
r.w.WriteString("\r")
+ r.prevLines = 0
+ r.partialLineLen = 0
+}
+
+func truncate(b []byte, width int) ([]byte, int) {
+ n := 0
+ for i := 0; i < len(b); {
+ if bytes.HasPrefix(b[i:], []byte("\033[")) {
+ // An escape sequence usually starts with [, then has one or two numbers
+ // separated by semicolon, and ends with some terminating character. To
+ // try and munch the whole sequence, skip over any numbers and
+ // semicolon here.
+ for i += 2; i < len(b)-1 && ('0' <= b[i] && b[i] <= '9' || b[i] == ';'); i++ {
+ }
+ // Skip the terminating character.
+ i++
+ continue
+ }
+ if n+1 > width {
+ return b[:i], n
+ }
+ _, runeWidth := utf8.DecodeRune(b[i:])
+ i += runeWidth
+ n++
+ }
+ return b, n
}
// Write implements io.Writer.
@@ -46,22 +75,26 @@ func (r *Renderer) Write(buf []byte) (int, error) {
for len(buf) > 0 {
i := bytes.IndexByte(buf, '\n')
if i < 0 {
- n, err := r.w.Write(buf)
- totalBytes += n
- return totalBytes, err
+ line, lineWidth := truncate(buf, r.width-r.partialLineLen)
+ r.partialLineLen += lineWidth
+ if n, err := r.w.Write(line); err != nil {
+ return totalBytes + n, err
+ }
+ totalBytes += len(buf)
+ return totalBytes, nil
}
- line := buf[:i]
+ line, _ := truncate(buf[:i], r.width)
buf = buf[i+1:]
- n, err := r.w.Write(line)
- totalBytes += n
- if err != nil {
- return totalBytes, err
+ if n, err := r.w.Write(line); err != nil {
+ return totalBytes + n, err
}
+ totalBytes += i
if _, err := r.w.WriteString("\033[K\n"); err != nil {
return totalBytes, err
}
totalBytes++
r.prevLines++
+ r.partialLineLen = 0
}
return totalBytes, nil
}