diff --git a/kittens/pager/file_input.go b/kittens/pager/file_input.go new file mode 100644 index 000000000..8c5950100 --- /dev/null +++ b/kittens/pager/file_input.go @@ -0,0 +1,71 @@ +// License: GPLv3 Copyright: 2024, Kovid Goyal, + +package pager + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "strings" + + "golang.org/x/sys/unix" +) + +var _ = fmt.Print + +func read_input(input_file *os.File, input_file_name string, input_channel chan<- input_line_struct) { + const buf_capacity = 8192 + var buf_array [buf_capacity]byte + output_buf := strings.Builder{} + output_buf.Grow(buf_capacity) + var err error + var n int + + defer func() { + _ = input_file.Close() + last := input_line_struct{line: output_buf.String(), err: err} + if errors.Is(err, io.EOF) { + last.err = nil + } + if len(last.line) > 0 || last.err != nil { + input_channel <- last + } + close(input_channel) + }() + + process_chunk := func(chunk []byte) { + for len(chunk) > 0 { + idx := bytes.IndexByte(chunk, '\n') + switch idx { + case -1: + _, _ = output_buf.Write(chunk) + chunk = nil + default: + _, _ = output_buf.Write(chunk[idx:]) + chunk = chunk[idx+1:] + input_channel <- input_line_struct{line: output_buf.String()} + output_buf.Reset() + output_buf.Grow(buf_capacity) + } + } + } + + read_with_retry := func(b []byte) (n int, err error) { + for { + n, err = input_file.Read(b) + if err != unix.EAGAIN && err != unix.EINTR { + break + } + } + return + } + + for err != nil { + n, err = read_with_retry(buf_array[:]) + if n > 0 { + process_chunk(buf_array[:n]) + } + } +} diff --git a/kittens/pager/main.go b/kittens/pager/main.go index 7535d6ac5..504a43c25 100644 --- a/kittens/pager/main.go +++ b/kittens/pager/main.go @@ -14,6 +14,7 @@ package pager import ( "fmt" + "os" "kitty/tools/cli" "kitty/tools/tty" @@ -23,7 +24,42 @@ var _ = fmt.Print var debugprintln = tty.DebugPrintln var _ = debugprintln +type input_line_struct struct { + line string + err error +} + +type global_state_struct struct { + input_file_name string + opts *Options +} + +var global_state global_state_struct + func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) { + global_state.opts = opts_ + input_channel := make(chan input_line_struct, 4096) + var input_file *os.File + if len(args) > 1 { + return 1, fmt.Errorf("Only a single file can be viewed at a time") + } + if len(args) == 0 { + if tty.IsTerminal(os.Stdin.Fd()) { + return 1, fmt.Errorf("STDIN is a terminal and no filename specified. See --help") + } + input_file = os.Stdin + global_state.input_file_name = "/dev/stdin" + } else { + input_file, err = os.Open(args[0]) + if err != nil { + return 1, err + } + if tty.IsTerminal(input_file.Fd()) { + return 1, fmt.Errorf("%s is a terminal not paging it", args[0]) + } + global_state.input_file_name = args[0] + } + go read_input(input_file, global_state.input_file_name, input_channel) return } diff --git a/kittens/pager/main.py b/kittens/pager/main.py index aa2772552..5541fdaae 100644 --- a/kittens/pager/main.py +++ b/kittens/pager/main.py @@ -14,7 +14,11 @@ choices=pager,scrollback The role the pager is used for. The default is a standard less like pager. '''.format -help_text = 'Display text in a pager with various features such as searching, copy/paste, etc. Text can some from the specified file or from STDIN.' +help_text = '''\ +Display text in a pager with various features such as searching, copy/paste, etc. +Text can some from the specified file or from STDIN. If no filename is specified +and STDIN is not a TTY, it is used. +''' usage = '[filename]'