github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/util/outputpager/page_output.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // This file incorporates work covered by the following copyright and
    16  // permission notice:
    17  //
    18  // Copyright 2016 Attic Labs, Inc. All rights reserved.
    19  // Licensed under the Apache License, version 2.0:
    20  // http://www.apache.org/licenses/LICENSE-2.0
    21  
    22  package outputpager
    23  
    24  import (
    25  	"io"
    26  	"os"
    27  	"os/exec"
    28  	"sync"
    29  
    30  	flag "github.com/juju/gnuflag"
    31  	goisatty "github.com/mattn/go-isatty"
    32  
    33  	"github.com/dolthub/dolt/go/store/d"
    34  )
    35  
    36  var (
    37  	noPager bool
    38  )
    39  
    40  type Pager struct {
    41  	Writer        io.Writer
    42  	stdin, stdout *os.File
    43  	mtx           *sync.Mutex
    44  	doneCh        chan struct{}
    45  }
    46  
    47  func Start() *Pager {
    48  	if noPager || !IsStdoutTty() {
    49  		return &Pager{os.Stdout, nil, nil, nil, nil}
    50  	}
    51  
    52  	lessPath, err := exec.LookPath("less")
    53  	d.Chk.NoError(err)
    54  
    55  	// -F ... Quit if entire file fits on first screen.
    56  	// -S ... Chop (truncate) long lines rather than wrapping.
    57  	// -R ... Output "raw" control characters.
    58  	// -X ... Don't use termcap init/deinit strings.
    59  	cmd := exec.Command(lessPath, "-FSRX")
    60  
    61  	stdin, stdout, err := os.Pipe()
    62  	d.Chk.NoError(err)
    63  	cmd.Stdout = os.Stdout
    64  	cmd.Stderr = os.Stderr
    65  	cmd.Stdin = stdin
    66  	cmd.Start()
    67  
    68  	p := &Pager{stdout, stdin, stdout, &sync.Mutex{}, make(chan struct{})}
    69  	go func() {
    70  		err := cmd.Wait()
    71  		d.Chk.NoError(err)
    72  		p.closePipe()
    73  		p.doneCh <- struct{}{}
    74  	}()
    75  	return p
    76  }
    77  
    78  func (p *Pager) Stop() {
    79  	if p.Writer != os.Stdout {
    80  		p.closePipe()
    81  		// Wait until less has fully exited, otherwise it might not have printed the terminal restore characters.
    82  		<-p.doneCh
    83  	}
    84  }
    85  
    86  func (p *Pager) closePipe() {
    87  	p.mtx.Lock()
    88  	defer p.mtx.Unlock()
    89  	if p.stdin != nil {
    90  		// Closing the pipe will cause any outstanding writes to stdout fail, and fail from now on.
    91  		p.stdin.Close()
    92  		p.stdout.Close()
    93  		p.stdin, p.stdout = nil, nil
    94  	}
    95  }
    96  
    97  func RegisterOutputpagerFlags(flags *flag.FlagSet) {
    98  	flags.BoolVar(&noPager, "no-pager", false, "suppress paging functionality")
    99  }
   100  
   101  func IsStdoutTty() bool {
   102  	return goisatty.IsTerminal(os.Stdout.Fd())
   103  }