github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/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 "fmt" 26 "io" 27 "os" 28 "os/exec" 29 "os/signal" 30 "sync" 31 "syscall" 32 33 flag "github.com/juju/gnuflag" 34 goisatty "github.com/mattn/go-isatty" 35 36 "github.com/dolthub/dolt/go/store/d" 37 ) 38 39 var ( 40 noPager bool 41 testing = false 42 ) 43 44 type Pager struct { 45 Writer io.Writer 46 stdin, stdout *os.File 47 mtx *sync.Mutex 48 doneCh chan struct{} 49 } 50 51 func Start() *Pager { 52 // `testing` is set to true only to test this function because when testing this function, stdout is not Terminal. 53 // otherwise, it must be always false. 54 if !testing { 55 if noPager || !IsStdoutTty() { 56 return &Pager{os.Stdout, nil, nil, nil, nil} 57 } 58 } 59 60 var lessPath string 61 var err error 62 var cmd *exec.Cmd 63 64 lessPath, err = exec.LookPath("less") 65 if err != nil { 66 lessPath, err = exec.LookPath("more") 67 d.Chk.NoError(err) 68 cmd = exec.Command(lessPath) 69 } else { 70 d.Chk.NoError(err) 71 // -F ... Quit if entire file fits on first screen. 72 // -S ... Chop (truncate) long lines rather than wrapping. 73 // -R ... Output "raw" control characters. 74 // -X ... Don't use termcap init/deinit strings. 75 cmd = exec.Command(lessPath, "-FSRX") 76 } 77 78 stdin, stdout, err := os.Pipe() 79 d.Chk.NoError(err) 80 cmd.Stdout = os.Stdout 81 cmd.Stderr = os.Stderr 82 cmd.Stdin = stdin 83 cmd.Start() 84 85 p := &Pager{stdout, stdin, stdout, &sync.Mutex{}, make(chan struct{})} 86 87 interruptChannel := make(chan os.Signal, 1) 88 signal.Notify(interruptChannel, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 89 90 go func() { 91 for { 92 select { 93 case _, ok := <-interruptChannel: 94 if ok { 95 p.closePipe() 96 p.doneCh <- struct{}{} 97 } 98 default: 99 } 100 } 101 }() 102 103 go func() { 104 err := cmd.Wait() 105 if err != nil { 106 fmt.Printf("error occurred during exit: %s ", err) 107 } 108 p.closePipe() 109 p.doneCh <- struct{}{} 110 }() 111 return p 112 } 113 114 func (p *Pager) Stop() { 115 if p.Writer != os.Stdout { 116 p.closePipe() 117 // Wait until less has fully exited, otherwise it might not have printed the terminal restore characters. 118 <-p.doneCh 119 } 120 } 121 122 func (p *Pager) closePipe() { 123 p.mtx.Lock() 124 defer p.mtx.Unlock() 125 if p.stdin != nil { 126 // Closing the pipe will cause any outstanding writes to stdout fail, and fail from now on. 127 p.stdin.Close() 128 p.stdout.Close() 129 p.stdin, p.stdout = nil, nil 130 } 131 } 132 133 func RegisterOutputpagerFlags(flags *flag.FlagSet) { 134 flags.BoolVar(&noPager, "no-pager", false, "suppress paging functionality") 135 } 136 137 func IsStdoutTty() bool { 138 return goisatty.IsTerminal(os.Stdout.Fd()) 139 } 140 141 func SetTestingArg(s bool) { 142 testing = s 143 }