github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/display/pager.go (about)

     1  package display
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"os/exec"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/karrick/gows"
    13  	"github.com/spf13/viper"
    14  	"github.com/turbot/steampipe/pkg/constants"
    15  	"github.com/turbot/steampipe/pkg/error_helpers"
    16  )
    17  
    18  // ShowPaged displays the `content` in a system dependent pager
    19  func ShowPaged(ctx context.Context, content string) {
    20  	if isPagerNeeded(content) && (runtime.GOOS == "darwin" || runtime.GOOS == "linux") {
    21  		nixPager(ctx, content)
    22  	} else {
    23  		nullPager(content)
    24  	}
    25  }
    26  
    27  func isPagerNeeded(content string) bool {
    28  	// only show pager in interactive mode
    29  	if !viper.GetBool(constants.ConfigKeyInteractive) {
    30  		return false
    31  	}
    32  
    33  	maxCols, maxRow, _ := gows.GetWinSize()
    34  
    35  	// let's scan through it instead of iterating over it fully
    36  	sc := bufio.NewScanner(strings.NewReader(content))
    37  
    38  	// explicitly allocate a large bugger for the scanner to use - otherwise we may fail for large rows
    39  	buffSize := 256 * 1024
    40  	buff := make([]byte, buffSize)
    41  	sc.Buffer(buff, buffSize)
    42  
    43  	lineCount := 0
    44  	for sc.Scan() {
    45  		line := sc.Text()
    46  		lineCount++
    47  		if lineCount > maxRow {
    48  			return true
    49  		}
    50  		if len(line) > maxCols {
    51  			return true
    52  		}
    53  	}
    54  	return false
    55  }
    56  
    57  func nullPager(content string) {
    58  	// just dump the whole thing out
    59  	// we will use this for non-tty environments as well
    60  	fmt.Print(content)
    61  }
    62  
    63  func nixPager(ctx context.Context, content string) {
    64  	if isLessAvailable() {
    65  		execPager(ctx, exec.Command("less", "-SRXF"), content)
    66  	} else if isMoreAvailable() {
    67  		execPager(ctx, exec.Command("more"), content)
    68  	} else {
    69  		nullPager(content)
    70  	}
    71  }
    72  
    73  func isLessAvailable() bool {
    74  	_, err := exec.LookPath("less")
    75  	return err == nil
    76  }
    77  
    78  func isMoreAvailable() bool {
    79  	_, err := exec.LookPath("more")
    80  	return err == nil
    81  }
    82  
    83  func execPager(ctx context.Context, cmd *exec.Cmd, content string) {
    84  	cmd.Stdout = os.Stdout
    85  	cmd.Stderr = os.Stderr
    86  	cmd.Stdin = strings.NewReader(content)
    87  	// run the command - it will block until the pager is exited
    88  	err := cmd.Run()
    89  	if err != nil {
    90  		error_helpers.ShowErrorWithMessage(ctx, err, "could not display results")
    91  	}
    92  }