Skip to content

Commit

Permalink
cmd: Pipe output through pager in terminals
Browse files Browse the repository at this point in the history
Replicate git's behavior of piping output through a pager when it
doesn't fit a single screen. This is not only more convenient than
piping into less on the command line, but preserves terminal features
like colored output as well.
  • Loading branch information
fmuellner committed Jan 25, 2021
1 parent d046b57 commit 2d65ce7
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 0 deletions.
3 changes: 3 additions & 0 deletions cmd/ci_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ lab ci status --wait`,

pid := rn

pager := NewPager(cmd.Flags())
defer pager.Close()

w := tabwriter.NewWriter(os.Stdout, 2, 4, 1, byte(' '), 0)

wait, err := cmd.Flags().GetBool("wait")
Expand Down
3 changes: 3 additions & 0 deletions cmd/ci_trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ var ciTraceCmd = &cobra.Command{
}
projectID = project.ID

pager := NewPager(cmd.Flags())
defer pager.Close()

err = doTrace(context.Background(), os.Stdout, projectID, pipelineID, jobName)
if err != nil {
log.Fatal(err)
Expand Down
4 changes: 4 additions & 0 deletions cmd/issue_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ lab issue list remote "search terms" # search "remote" for issues with "search
if err != nil {
log.Fatal(err)
}

pager := NewPager(cmd.Flags())
defer pager.Close()

for _, issue := range issues {
fmt.Printf("#%d %s\n", issue.IID, issue.Title)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/issue_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ var issueShowCmd = &cobra.Command{
renderMarkdown = !noMarkdown
}

pager := NewPager(cmd.Flags())
defer pager.Close()

printIssue(issue, rn, renderMarkdown)

showComments, _ := cmd.Flags().GetBool("comments")
Expand Down
3 changes: 3 additions & 0 deletions cmd/label_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ lab label list remote "search term" # search "remote" for labels with "search t
log.Fatal(err)
}

pager := NewPager(cmd.Flags())
defer pager.Close()

for _, label := range labels {
// GitLab API has no search for labels, so we do it ourselves
if labelSearch != "" &&
Expand Down
4 changes: 4 additions & 0 deletions cmd/mr_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ lab mr list remote "search terms" # search "remote" for merge requests with "se
log.Print(err)
config.UserConfigError()
}

pager := NewPager(cmd.Flags())
defer pager.Close()

for _, mr := range mrs {
fmt.Printf("!%d %s\n", mr.IID, mr.Title)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/mr_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ var mrShowCmd = &cobra.Command{
}
git.Show(remote+"/"+mr.TargetBranch, mr.SHA, mrShowPatchReverse)
} else {
pager := NewPager(cmd.Flags())
defer pager.Close()

printMR(mr, rn, renderMarkdown)
}

Expand Down
4 changes: 4 additions & 0 deletions cmd/project_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ var projectListCmd = &cobra.Command{
if err != nil {
log.Fatal(err)
}

pager := NewPager(cmd.Flags())
defer pager.Close()

for _, p := range projects {
fmt.Println(p.PathWithNamespace)
}
Expand Down
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func init() {
RootCmd.SetHelpCommand(helpCmd)
RootCmd.SetHelpFunc(helpFunc)
RootCmd.Flags().Bool("version", false, "Show the lab version")
RootCmd.PersistentFlags().Bool("no-pager", false, "Do not pipe output into a pager")
}

var (
Expand Down
2 changes: 2 additions & 0 deletions cmd/snippet_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ var snippetListCmd = &cobra.Command{
if err != nil {
log.Fatal(err)
}
pager := NewPager(cmd.Flags())
defer pager.Close()
for _, snip := range snips {
fmt.Printf("#%d %s\n", snip.ID, snip.Title)
}
Expand Down
45 changes: 45 additions & 0 deletions cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -419,6 +420,50 @@ func isOutputTerminal() bool {
return true
}

type Pager struct {
proc *os.Process
stdout int
}

// If standard output is a terminal, redirect output to an external
// pager until the returned object's Close() method is called
func NewPager(fs *flag.FlagSet) *Pager {
cmdLine, env := git.PagerCommand()
args := strings.Split(cmdLine, " ")

noPager, _ := fs.GetBool("no-pager")
if !isOutputTerminal() || noPager || args[0] == "cat" {
return &Pager{}
}

pr, pw, _ := os.Pipe()
defer pw.Close()

name, _ := exec.LookPath(args[0])
proc, _ := os.StartProcess(name, args, &os.ProcAttr{
Env: env,
Files: []*os.File{pr, os.Stdout, os.Stderr},
})

savedStdout, _ := syscall.Dup(syscall.Stdout)
_ = syscall.Dup2(int(pw.Fd()), syscall.Stdout)

return &Pager{
proc: proc,
stdout: savedStdout,
}
}

func (pager *Pager) Close() {
if pager.stdout > 0 {
_ = syscall.Dup2(pager.stdout, syscall.Stdout)
_ = syscall.Close(pager.stdout)
}
if pager.proc != nil {
pager.proc.Wait()
}
}

func LabPersistentPreRun(cmd *cobra.Command, args []string) {
flagConfig(cmd.Flags())
}
Expand Down
31 changes: 31 additions & 0 deletions internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,37 @@ func CommentChar() string {
return "#"
}

// PagerCommand returns the commandline and environment for the pager
func PagerCommand() (string, []string) {
// Set up environment for common pagers, see the documentation
// for "core.pager" in git-config(1)
env := os.Environ()
if _, ok := os.LookupEnv("LESS"); !ok {
env = append(env, "LESS=FRX")
}
if _, ok := os.LookupEnv("LESSSECURE"); !ok {
env = append(env, "LESSSECURE=1")
}
if _, ok := os.LookupEnv("LV"); !ok {
env = append(env, "LV=-c")
}

// Find an appropriate pager command, following git's preference
cmd, ok := os.LookupEnv("GIT_PAGER")
if ok {
return cmd, env
}
cmd, err := gitconfig.Entire("core.pager")
if err == nil {
return cmd, env
}
cmd, ok = os.LookupEnv("PAGER")
if ok {
return cmd, env
}
return "less", env
}

// LastCommitMessage returns the last commits message as one line
func LastCommitMessage() (string, error) {
cmd := New("show", "-s", "--format=%s%n%+b", "HEAD")
Expand Down

0 comments on commit 2d65ce7

Please sign in to comment.