feat: add pager support to bd list (bd-jdz3)
Add pager support following gh cli conventions: Flags: - --no-pager: disable pager for this command Environment variables: - BD_PAGER / PAGER: pager program (default: less) - BD_NO_PAGER: disable pager globally Behavior: - Auto-enable pager when output exceeds terminal height - Respect LESS env var for pager options - Disable pager when stdout is not a TTY (pipes/scripts) Implementation: - New internal/ui/pager.go with ToPager() function - Added formatIssueLong/formatIssueCompact helper functions - Buffer output before displaying to support pager 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
180
cmd/bd/list.go
180
cmd/bd/list.go
@@ -306,6 +306,61 @@ func sortIssues(issues []*types.Issue, sortBy string, reverse bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// formatIssueLong formats a single issue in long format to a buffer
|
||||||
|
func formatIssueLong(buf *strings.Builder, issue *types.Issue, labels []string) {
|
||||||
|
status := string(issue.Status)
|
||||||
|
if status == "closed" {
|
||||||
|
line := fmt.Sprintf("%s%s [P%d] [%s] %s\n %s",
|
||||||
|
pinIndicator(issue), issue.ID, issue.Priority,
|
||||||
|
issue.IssueType, status, issue.Title)
|
||||||
|
buf.WriteString(ui.RenderClosedLine(line))
|
||||||
|
buf.WriteString("\n")
|
||||||
|
} else {
|
||||||
|
buf.WriteString(fmt.Sprintf("%s%s [%s] [%s] %s\n",
|
||||||
|
pinIndicator(issue),
|
||||||
|
ui.RenderID(issue.ID),
|
||||||
|
ui.RenderPriority(issue.Priority),
|
||||||
|
ui.RenderType(string(issue.IssueType)),
|
||||||
|
ui.RenderStatus(status)))
|
||||||
|
buf.WriteString(fmt.Sprintf(" %s\n", issue.Title))
|
||||||
|
}
|
||||||
|
if issue.Assignee != "" {
|
||||||
|
buf.WriteString(fmt.Sprintf(" Assignee: %s\n", issue.Assignee))
|
||||||
|
}
|
||||||
|
if len(labels) > 0 {
|
||||||
|
buf.WriteString(fmt.Sprintf(" Labels: %v\n", labels))
|
||||||
|
}
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatIssueCompact formats a single issue in compact format to a buffer
|
||||||
|
func formatIssueCompact(buf *strings.Builder, issue *types.Issue, labels []string) {
|
||||||
|
labelsStr := ""
|
||||||
|
if len(labels) > 0 {
|
||||||
|
labelsStr = fmt.Sprintf(" %v", labels)
|
||||||
|
}
|
||||||
|
assigneeStr := ""
|
||||||
|
if issue.Assignee != "" {
|
||||||
|
assigneeStr = fmt.Sprintf(" @%s", issue.Assignee)
|
||||||
|
}
|
||||||
|
status := string(issue.Status)
|
||||||
|
if status == "closed" {
|
||||||
|
line := fmt.Sprintf("%s%s [P%d] [%s] %s%s%s - %s",
|
||||||
|
pinIndicator(issue), issue.ID, issue.Priority,
|
||||||
|
issue.IssueType, status, assigneeStr, labelsStr, issue.Title)
|
||||||
|
buf.WriteString(ui.RenderClosedLine(line))
|
||||||
|
buf.WriteString("\n")
|
||||||
|
} else {
|
||||||
|
buf.WriteString(fmt.Sprintf("%s%s [%s] [%s] %s%s%s - %s\n",
|
||||||
|
pinIndicator(issue),
|
||||||
|
ui.RenderID(issue.ID),
|
||||||
|
ui.RenderPriority(issue.Priority),
|
||||||
|
ui.RenderType(string(issue.IssueType)),
|
||||||
|
ui.RenderStatus(status),
|
||||||
|
assigneeStr, labelsStr, issue.Title))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var listCmd = &cobra.Command{
|
var listCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
GroupID: "issues",
|
GroupID: "issues",
|
||||||
@@ -373,6 +428,9 @@ var listCmd = &cobra.Command{
|
|||||||
prettyFormat, _ := cmd.Flags().GetBool("pretty")
|
prettyFormat, _ := cmd.Flags().GetBool("pretty")
|
||||||
watchMode, _ := cmd.Flags().GetBool("watch")
|
watchMode, _ := cmd.Flags().GetBool("watch")
|
||||||
|
|
||||||
|
// Pager control (bd-jdz3)
|
||||||
|
noPager, _ := cmd.Flags().GetBool("no-pager")
|
||||||
|
|
||||||
// Watch mode implies pretty format
|
// Watch mode implies pretty format
|
||||||
if watchMode {
|
if watchMode {
|
||||||
prettyFormat = true
|
prettyFormat = true
|
||||||
@@ -683,63 +741,26 @@ var listCmd = &cobra.Command{
|
|||||||
// Apply sorting
|
// Apply sorting
|
||||||
sortIssues(issues, sortBy, reverse)
|
sortIssues(issues, sortBy, reverse)
|
||||||
|
|
||||||
|
// Build output in buffer for pager support (bd-jdz3)
|
||||||
|
var buf strings.Builder
|
||||||
if longFormat {
|
if longFormat {
|
||||||
// Long format: multi-line with details
|
// Long format: multi-line with details
|
||||||
fmt.Printf("\nFound %d issues:\n\n", len(issues))
|
buf.WriteString(fmt.Sprintf("\nFound %d issues:\n\n", len(issues)))
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
status := string(issue.Status)
|
formatIssueLong(&buf, issue, issue.Labels)
|
||||||
if status == "closed" {
|
|
||||||
// Entire closed issue is dimmed
|
|
||||||
line := fmt.Sprintf("%s%s [P%d] [%s] %s\n %s",
|
|
||||||
pinIndicator(issue), issue.ID, issue.Priority,
|
|
||||||
issue.IssueType, status, issue.Title)
|
|
||||||
fmt.Println(ui.RenderClosedLine(line))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s%s [%s] [%s] %s\n",
|
|
||||||
pinIndicator(issue),
|
|
||||||
ui.RenderID(issue.ID),
|
|
||||||
ui.RenderPriority(issue.Priority),
|
|
||||||
ui.RenderType(string(issue.IssueType)),
|
|
||||||
ui.RenderStatus(status))
|
|
||||||
fmt.Printf(" %s\n", issue.Title)
|
|
||||||
}
|
|
||||||
if issue.Assignee != "" {
|
|
||||||
fmt.Printf(" Assignee: %s\n", issue.Assignee)
|
|
||||||
}
|
|
||||||
if len(issue.Labels) > 0 {
|
|
||||||
fmt.Printf(" Labels: %v\n", issue.Labels)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Compact format: one line per issue
|
// Compact format: one line per issue
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
labelsStr := ""
|
formatIssueCompact(&buf, issue, issue.Labels)
|
||||||
if len(issue.Labels) > 0 {
|
|
||||||
labelsStr = fmt.Sprintf(" %v", issue.Labels)
|
|
||||||
}
|
|
||||||
assigneeStr := ""
|
|
||||||
if issue.Assignee != "" {
|
|
||||||
assigneeStr = fmt.Sprintf(" @%s", issue.Assignee)
|
|
||||||
}
|
|
||||||
status := string(issue.Status)
|
|
||||||
if status == "closed" {
|
|
||||||
// Entire closed line is dimmed
|
|
||||||
line := fmt.Sprintf("%s%s [P%d] [%s] %s%s%s - %s",
|
|
||||||
pinIndicator(issue), issue.ID, issue.Priority,
|
|
||||||
issue.IssueType, status, assigneeStr, labelsStr, issue.Title)
|
|
||||||
fmt.Println(ui.RenderClosedLine(line))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s%s [%s] [%s] %s%s%s - %s\n",
|
|
||||||
pinIndicator(issue),
|
|
||||||
ui.RenderID(issue.ID),
|
|
||||||
ui.RenderPriority(issue.Priority),
|
|
||||||
ui.RenderType(string(issue.IssueType)),
|
|
||||||
ui.RenderStatus(status),
|
|
||||||
assigneeStr, labelsStr, issue.Title)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Output with pager support
|
||||||
|
if err := ui.ToPager(buf.String(), ui.PagerOptions{NoPager: noPager}); err != nil {
|
||||||
|
fmt.Fprint(os.Stdout, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
// Show truncation hint if we hit the limit (GH#788)
|
// Show truncation hint if we hit the limit (GH#788)
|
||||||
if effectiveLimit > 0 && len(issues) == effectiveLimit {
|
if effectiveLimit > 0 && len(issues) == effectiveLimit {
|
||||||
fmt.Fprintf(os.Stderr, "\nShowing %d issues (use --limit 0 for all)\n", effectiveLimit)
|
fmt.Fprintf(os.Stderr, "\nShowing %d issues (use --limit 0 for all)\n", effectiveLimit)
|
||||||
@@ -836,68 +857,28 @@ var listCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
labelsMap, _ := store.GetLabelsForIssues(ctx, issueIDs)
|
labelsMap, _ := store.GetLabelsForIssues(ctx, issueIDs)
|
||||||
|
|
||||||
|
// Build output in buffer for pager support (bd-jdz3)
|
||||||
|
var buf strings.Builder
|
||||||
if longFormat {
|
if longFormat {
|
||||||
// Long format: multi-line with details
|
// Long format: multi-line with details
|
||||||
fmt.Printf("\nFound %d issues:\n\n", len(issues))
|
buf.WriteString(fmt.Sprintf("\nFound %d issues:\n\n", len(issues)))
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
labels := labelsMap[issue.ID]
|
labels := labelsMap[issue.ID]
|
||||||
status := string(issue.Status)
|
formatIssueLong(&buf, issue, labels)
|
||||||
|
|
||||||
if status == "closed" {
|
|
||||||
// Entire closed issue is dimmed
|
|
||||||
line := fmt.Sprintf("%s%s [P%d] [%s] %s\n %s",
|
|
||||||
pinIndicator(issue), issue.ID, issue.Priority,
|
|
||||||
issue.IssueType, status, issue.Title)
|
|
||||||
fmt.Println(ui.RenderClosedLine(line))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s%s [%s] [%s] %s\n",
|
|
||||||
pinIndicator(issue),
|
|
||||||
ui.RenderID(issue.ID),
|
|
||||||
ui.RenderPriority(issue.Priority),
|
|
||||||
ui.RenderType(string(issue.IssueType)),
|
|
||||||
ui.RenderStatus(status))
|
|
||||||
fmt.Printf(" %s\n", issue.Title)
|
|
||||||
}
|
|
||||||
if issue.Assignee != "" {
|
|
||||||
fmt.Printf(" Assignee: %s\n", issue.Assignee)
|
|
||||||
}
|
|
||||||
if len(labels) > 0 {
|
|
||||||
fmt.Printf(" Labels: %v\n", labels)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Compact format: one line per issue
|
// Compact format: one line per issue
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
labels := labelsMap[issue.ID]
|
labels := labelsMap[issue.ID]
|
||||||
|
formatIssueCompact(&buf, issue, labels)
|
||||||
labelsStr := ""
|
|
||||||
if len(labels) > 0 {
|
|
||||||
labelsStr = fmt.Sprintf(" %v", labels)
|
|
||||||
}
|
|
||||||
assigneeStr := ""
|
|
||||||
if issue.Assignee != "" {
|
|
||||||
assigneeStr = fmt.Sprintf(" @%s", issue.Assignee)
|
|
||||||
}
|
|
||||||
status := string(issue.Status)
|
|
||||||
if status == "closed" {
|
|
||||||
// Entire closed line is dimmed
|
|
||||||
line := fmt.Sprintf("%s%s [P%d] [%s] %s%s%s - %s",
|
|
||||||
pinIndicator(issue), issue.ID, issue.Priority,
|
|
||||||
issue.IssueType, status, assigneeStr, labelsStr, issue.Title)
|
|
||||||
fmt.Println(ui.RenderClosedLine(line))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s%s [%s] [%s] %s%s%s - %s\n",
|
|
||||||
pinIndicator(issue),
|
|
||||||
ui.RenderID(issue.ID),
|
|
||||||
ui.RenderPriority(issue.Priority),
|
|
||||||
ui.RenderType(string(issue.IssueType)),
|
|
||||||
ui.RenderStatus(status),
|
|
||||||
assigneeStr, labelsStr, issue.Title)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Output with pager support
|
||||||
|
if err := ui.ToPager(buf.String(), ui.PagerOptions{NoPager: noPager}); err != nil {
|
||||||
|
fmt.Fprint(os.Stdout, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
// Show truncation hint if we hit the limit (GH#788)
|
// Show truncation hint if we hit the limit (GH#788)
|
||||||
if effectiveLimit > 0 && len(issues) == effectiveLimit {
|
if effectiveLimit > 0 && len(issues) == effectiveLimit {
|
||||||
fmt.Fprintf(os.Stderr, "\nShowing %d issues (use --limit 0 for all)\n", effectiveLimit)
|
fmt.Fprintf(os.Stderr, "\nShowing %d issues (use --limit 0 for all)\n", effectiveLimit)
|
||||||
@@ -963,6 +944,9 @@ func init() {
|
|||||||
listCmd.Flags().Bool("pretty", false, "Display issues in a tree format with status/priority symbols")
|
listCmd.Flags().Bool("pretty", false, "Display issues in a tree format with status/priority symbols")
|
||||||
listCmd.Flags().BoolP("watch", "w", false, "Watch for changes and auto-update display (implies --pretty)")
|
listCmd.Flags().BoolP("watch", "w", false, "Watch for changes and auto-update display (implies --pretty)")
|
||||||
|
|
||||||
|
// Pager control (bd-jdz3)
|
||||||
|
listCmd.Flags().Bool("no-pager", false, "Disable pager output")
|
||||||
|
|
||||||
// Note: --json flag is defined as a persistent flag in main.go, not here
|
// Note: --json flag is defined as a persistent flag in main.go, not here
|
||||||
rootCmd.AddCommand(listCmd)
|
rootCmd.AddCommand(listCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
119
internal/ui/pager.go
Normal file
119
internal/ui/pager.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// Package ui provides terminal styling and pager support for beads CLI output.
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PagerOptions controls pager behavior
|
||||||
|
type PagerOptions struct {
|
||||||
|
// NoPager disables pager for this command (--no-pager flag)
|
||||||
|
NoPager bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldUsePager determines if output should be piped to a pager.
|
||||||
|
// Returns false if:
|
||||||
|
// - NoPager option is set
|
||||||
|
// - BD_NO_PAGER environment variable is set
|
||||||
|
// - stdout is not a TTY (e.g., piped to another command)
|
||||||
|
func shouldUsePager(opts PagerOptions) bool {
|
||||||
|
if opts.NoPager {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("BD_NO_PAGER") != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if stdout is a terminal
|
||||||
|
if !term.IsTerminal(int(os.Stdout.Fd())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPagerCommand returns the pager command to use.
|
||||||
|
// Checks BD_PAGER, then PAGER, defaults to "less".
|
||||||
|
func getPagerCommand() string {
|
||||||
|
if pager := os.Getenv("BD_PAGER"); pager != "" {
|
||||||
|
return pager
|
||||||
|
}
|
||||||
|
if pager := os.Getenv("PAGER"); pager != "" {
|
||||||
|
return pager
|
||||||
|
}
|
||||||
|
return "less"
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTerminalHeight returns the height of the terminal in lines.
|
||||||
|
// Returns 0 if unable to determine (not a TTY).
|
||||||
|
func getTerminalHeight() int {
|
||||||
|
fd := int(os.Stdout.Fd())
|
||||||
|
if !term.IsTerminal(fd) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_, height, err := term.GetSize(fd)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
// contentHeight counts the number of lines in the content.
|
||||||
|
func contentHeight(content string) int {
|
||||||
|
if content == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return strings.Count(content, "\n") + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPager pipes content to a pager if appropriate.
|
||||||
|
// If pager should not be used (not a TTY, --no-pager, etc.), prints directly.
|
||||||
|
// If content fits in terminal, prints directly without pager.
|
||||||
|
func ToPager(content string, opts PagerOptions) error {
|
||||||
|
if !shouldUsePager(opts) {
|
||||||
|
fmt.Print(content)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if content exceeds terminal height
|
||||||
|
termHeight := getTerminalHeight()
|
||||||
|
if termHeight > 0 && contentHeight(content) <= termHeight-1 {
|
||||||
|
// Content fits in terminal, no pager needed
|
||||||
|
fmt.Print(content)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use pager
|
||||||
|
pagerCmd := getPagerCommand()
|
||||||
|
|
||||||
|
// Parse pager command (may include arguments like "less -R")
|
||||||
|
parts := strings.Fields(pagerCmd)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
fmt.Print(content)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(parts[0], parts[1:]...)
|
||||||
|
cmd.Stdin = strings.NewReader(content)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
// Set LESS environment variable for sensible defaults if not already set
|
||||||
|
// -R: Allow ANSI color codes
|
||||||
|
// -F: Quit if content fits on one screen
|
||||||
|
// -X: Don't clear screen on exit
|
||||||
|
if os.Getenv("LESS") == "" {
|
||||||
|
cmd.Env = append(os.Environ(), "LESS=-RFX")
|
||||||
|
} else {
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
174
internal/ui/pager_test.go
Normal file
174
internal/ui/pager_test.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContentHeight(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
content: "",
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single line",
|
||||||
|
content: "hello",
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single line with newline",
|
||||||
|
content: "hello\n",
|
||||||
|
want: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple lines",
|
||||||
|
content: "line1\nline2\nline3",
|
||||||
|
want: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple lines with trailing newline",
|
||||||
|
content: "line1\nline2\nline3\n",
|
||||||
|
want: 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := contentHeight(tt.content)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("contentHeight() = %d, want %d", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldUsePager(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
opts PagerOptions
|
||||||
|
envVars map[string]string
|
||||||
|
wantPager bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoPager option set",
|
||||||
|
opts: PagerOptions{NoPager: true},
|
||||||
|
wantPager: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BD_NO_PAGER env set",
|
||||||
|
opts: PagerOptions{NoPager: false},
|
||||||
|
envVars: map[string]string{"BD_NO_PAGER": "1"},
|
||||||
|
wantPager: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Set up env vars
|
||||||
|
for k, v := range tt.envVars {
|
||||||
|
oldVal := os.Getenv(k)
|
||||||
|
os.Setenv(k, v)
|
||||||
|
defer os.Setenv(k, oldVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := shouldUsePager(tt.opts)
|
||||||
|
if got != tt.wantPager {
|
||||||
|
t.Errorf("shouldUsePager() = %v, want %v", got, tt.wantPager)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPagerCommand(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
envVars map[string]string
|
||||||
|
wantPager string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default pager",
|
||||||
|
envVars: map[string]string{},
|
||||||
|
wantPager: "less",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BD_PAGER set",
|
||||||
|
envVars: map[string]string{"BD_PAGER": "more"},
|
||||||
|
wantPager: "more",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PAGER set",
|
||||||
|
envVars: map[string]string{"PAGER": "cat"},
|
||||||
|
wantPager: "cat",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BD_PAGER takes precedence over PAGER",
|
||||||
|
envVars: map[string]string{"BD_PAGER": "more", "PAGER": "cat"},
|
||||||
|
wantPager: "more",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Save and clear relevant env vars
|
||||||
|
oldBdPager := os.Getenv("BD_PAGER")
|
||||||
|
oldPager := os.Getenv("PAGER")
|
||||||
|
os.Unsetenv("BD_PAGER")
|
||||||
|
os.Unsetenv("PAGER")
|
||||||
|
defer func() {
|
||||||
|
if oldBdPager != "" {
|
||||||
|
os.Setenv("BD_PAGER", oldBdPager)
|
||||||
|
}
|
||||||
|
if oldPager != "" {
|
||||||
|
os.Setenv("PAGER", oldPager)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set up env vars for test
|
||||||
|
for k, v := range tt.envVars {
|
||||||
|
os.Setenv(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := getPagerCommand()
|
||||||
|
if got != tt.wantPager {
|
||||||
|
t.Errorf("getPagerCommand() = %q, want %q", got, tt.wantPager)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToPagerNoPagerOption(t *testing.T) {
|
||||||
|
// Create a test output that we want to capture
|
||||||
|
content := "test content\n"
|
||||||
|
|
||||||
|
// With NoPager=true, ToPager should just print directly
|
||||||
|
// (we can't easily capture stdout in a test, but we can verify no error)
|
||||||
|
err := ToPager(content, PagerOptions{NoPager: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ToPager() returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToPagerWithBdNoPagerEnv(t *testing.T) {
|
||||||
|
oldVal := os.Getenv("BD_NO_PAGER")
|
||||||
|
os.Setenv("BD_NO_PAGER", "1")
|
||||||
|
defer func() {
|
||||||
|
if oldVal != "" {
|
||||||
|
os.Setenv("BD_NO_PAGER", oldVal)
|
||||||
|
} else {
|
||||||
|
os.Unsetenv("BD_NO_PAGER")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
content := strings.Repeat("line\n", 100)
|
||||||
|
err := ToPager(content, PagerOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ToPager() returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user