command: adapt the output of the bug list to the terminal size

Michael Muré created

Change summary

commands/bug/bug.go             | 25 ++++++++++++++++++++++---
commands/execenv/env.go         | 13 +++++++++++++
commands/execenv/env_testing.go | 12 ++++++++----
entity/id.go                    |  4 ++--
entity/id_interleaved.go        |  2 +-
5 files changed, 46 insertions(+), 10 deletions(-)

Detailed changes

commands/bug/bug.go 🔗

@@ -15,6 +15,7 @@ import (
 	"github.com/MichaelMure/git-bug/commands/execenv"
 	"github.com/MichaelMure/git-bug/entities/bug"
 	"github.com/MichaelMure/git-bug/entities/common"
+	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/query"
 	"github.com/MichaelMure/git-bug/util/colors"
 )
@@ -233,6 +234,24 @@ func bugsIDFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
 }
 
 func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
+	width := env.Out.Width()
+	widthId := entity.HumanIdLength
+	widthStatus := len("closed")
+	widthComment := 6
+
+	widthRemaining := width -
+		widthId - 1 -
+		widthStatus - 1 -
+		widthComment - 1
+
+	widthTitle := int(float32(widthRemaining-3) * 0.7)
+	if widthTitle < 0 {
+		widthTitle = 0
+	}
+
+	widthRemaining = widthRemaining - widthTitle - 3 - 2
+	widthAuthor := widthRemaining
+
 	for _, b := range bugExcerpts {
 		author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
 		if err != nil {
@@ -249,8 +268,8 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
 
 		// truncate + pad if needed
 		labelsFmt := text.TruncateMax(labelsTxt.String(), 10)
-		titleFmt := text.LeftPadMaxLine(strings.TrimSpace(b.Title), 50-text.Len(labelsFmt), 0)
-		authorFmt := text.LeftPadMaxLine(author.DisplayName(), 15, 0)
+		titleFmt := text.LeftPadMaxLine(strings.TrimSpace(b.Title), widthTitle-text.Len(labelsFmt), 0)
+		authorFmt := text.LeftPadMaxLine(author.DisplayName(), widthAuthor, 0)
 
 		comments := fmt.Sprintf("%3d 💬", b.LenComments-1)
 		if b.LenComments-1 <= 0 {
@@ -260,7 +279,7 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
 			comments = "  ∞ 💬"
 		}
 
-		env.Out.Printf("%s\t%s\t%s\t%s\t%s\n",
+		env.Out.Printf("%s\t%s\t%s   %s %s\n",
 			colors.Cyan(b.Id().Human()),
 			colors.Yellow(b.Status),
 			titleFmt+labelsFmt,

commands/execenv/env.go 🔗

@@ -7,6 +7,7 @@ import (
 	"os"
 
 	"github.com/mattn/go-isatty"
+	"golang.org/x/term"
 
 	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/repository"
@@ -57,6 +58,8 @@ type Out interface {
 	// IsTerminal tells if the output is a user terminal (rather than a buffer,
 	// a pipe ...), which tells if we can use colors and other interactive features.
 	IsTerminal() bool
+	// Width return the width of the attached terminal, or a good enough value.
+	Width() int
 
 	// Raw return the underlying io.Writer, or itself if not.
 	// This is useful if something need to access the raw file descriptor.
@@ -123,6 +126,16 @@ func (o out) IsTerminal() bool {
 	return false
 }
 
+func (o out) Width() int {
+	if f, ok := o.Raw().(*os.File); ok {
+		width, _, err := term.GetSize(int(f.Fd()))
+		if err == nil {
+			return width
+		}
+	}
+	return 80
+}
+
 func (o out) Raw() io.Writer {
 	return o.Writer
 }

commands/execenv/env_testing.go 🔗

@@ -20,14 +20,14 @@ type TestIn struct {
 	forceIsTerminal bool
 }
 
-func (t *TestIn) ForceIsTerminal(value bool) {
-	t.forceIsTerminal = value
-}
-
 func (t *TestIn) IsTerminal() bool {
 	return t.forceIsTerminal
 }
 
+func (t *TestIn) ForceIsTerminal(value bool) {
+	t.forceIsTerminal = value
+}
+
 var _ Out = &TestOut{}
 
 type TestOut struct {
@@ -60,6 +60,10 @@ func (te *TestOut) IsTerminal() bool {
 	return te.forceIsTerminal
 }
 
+func (te *TestOut) Width() int {
+	return 80
+}
+
 func (te *TestOut) Raw() io.Writer {
 	return te.Buffer
 }

entity/id.go 🔗

@@ -11,7 +11,7 @@ import (
 
 // sha-256
 const idLength = 64
-const humanIdLength = 7
+const HumanIdLength = 7
 
 const UnsetId = Id("unset")
 
@@ -34,7 +34,7 @@ func (i Id) String() string {
 
 // Human return the identifier, shortened for human consumption
 func (i Id) Human() string {
-	format := fmt.Sprintf("%%.%ds", humanIdLength)
+	format := fmt.Sprintf("%%.%ds", HumanIdLength)
 	return fmt.Sprintf(format, i)
 }
 

entity/id_interleaved.go 🔗

@@ -22,7 +22,7 @@ func (ci CombinedId) String() string {
 
 // Human return the identifier, shortened for human consumption
 func (ci CombinedId) Human() string {
-	format := fmt.Sprintf("%%.%ds", humanIdLength)
+	format := fmt.Sprintf("%%.%ds", HumanIdLength)
 	return fmt.Sprintf(format, ci)
 }