More work on pager kitten

This commit is contained in:
Kovid Goyal 2024-01-21 14:47:56 +05:30
parent 1aa4d7d24b
commit b3e74de390
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
3 changed files with 96 additions and 24 deletions

View file

@ -9,23 +9,41 @@ import (
"io"
"os"
"strings"
"time"
"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) {
func wait_for_file_to_grow(file_name string, limit int64) (err error) {
// TODO: Use the fsnotify package to avoid this poll
for {
time.Sleep(time.Second)
s, err := os.Stat(file_name)
if err != nil {
return err
}
if s.Size() > limit {
break
}
}
return
}
func read_input(input_file *os.File, input_file_name string, input_channel chan<- input_line_struct, follow bool, count_carriage_returns bool) {
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
var total_read int64
var num_carriage_returns int
defer func() {
_ = input_file.Close()
last := input_line_struct{line: output_buf.String(), err: err}
last := input_line_struct{line: output_buf.String(), err: err, num_carriage_returns: num_carriage_returns}
if errors.Is(err, io.EOF) {
last.err = nil
}
@ -35,30 +53,73 @@ func read_input(input_file *os.File, input_file_name string, input_channel chan<
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)
var process_chunk func([]byte)
if count_carriage_returns {
process_chunk = func(chunk []byte) {
for _, ch := range chunk {
switch ch {
case '\r':
num_carriage_returns += 1
default:
_ = output_buf.WriteByte(ch)
case '\n':
input_channel <- input_line_struct{line: output_buf.String(), num_carriage_returns: num_carriage_returns, is_a_complete_line: true}
num_carriage_returns = 0
output_buf.Reset()
output_buf.Grow(buf_capacity)
}
}
}
} else {
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(), is_a_complete_line: true}
output_buf.Reset()
output_buf.Grow(buf_capacity)
}
}
}
}
for err != nil {
n, err = input_file.Read(buf_array[:])
if n > 0 {
process_chunk(buf_array[:n])
for {
for err != nil {
n, err = input_file.Read(buf_array[:])
if n > 0 {
total_read += int64(n)
process_chunk(buf_array[:n])
}
if err == unix.EAGAIN || err == unix.EINTR {
err = nil
}
}
if err == unix.EAGAIN || err == unix.EINTR {
err = nil
if !follow {
break
}
if errors.Is(err, io.EOF) {
input_file.Close()
if err = wait_for_file_to_grow(input_file_name, total_read); err != nil {
break
}
if input_file, err = os.Open(input_file_name); err != nil {
break
}
var off int64
if off, err = input_file.Seek(total_read, io.SeekStart); err != nil {
break
}
if off != total_read {
err = fmt.Errorf("Failed to seek in %s to: %d", input_file_name, off)
break
}
}
}
}

View file

@ -25,8 +25,10 @@ var debugprintln = tty.DebugPrintln
var _ = debugprintln
type input_line_struct struct {
line string
err error
line string
num_carriage_returns int
is_a_complete_line bool
err error
}
type global_state_struct struct {
@ -59,7 +61,11 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) {
}
global_state.input_file_name = args[0]
}
go read_input(input_file, global_state.input_file_name, input_channel)
follow := global_state.opts.Follow
if follow && global_state.input_file_name == "/dev/stdin" {
follow = false
}
go read_input(input_file, global_state.input_file_name, input_channel, follow, global_state.opts.Role == "scrollback")
return
}

View file

@ -12,6 +12,11 @@
default=pager
choices=pager,scrollback
The role the pager is used for. The default is a standard less like pager.
--follow
type=bool-set
Follow changes in the specified file, automatically scrolling if currently on the last line.
'''.format
help_text = '''\