2019-06-10 22:50:31 +03:00
|
|
|
package m
|
|
|
|
|
2019-06-11 19:29:30 +03:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/gdamore/tcell"
|
|
|
|
)
|
|
|
|
|
2019-06-10 22:50:31 +03:00
|
|
|
// Pager is the main on-screen pager
|
2019-06-11 16:31:26 +03:00
|
|
|
type _Pager struct {
|
2019-06-12 22:55:09 +03:00
|
|
|
reader _Reader
|
|
|
|
screen tcell.Screen
|
|
|
|
quit chan struct{}
|
|
|
|
firstLineOneBased int
|
2019-06-10 22:50:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewPager creates a new Pager
|
2019-06-11 16:31:26 +03:00
|
|
|
func NewPager(r _Reader) *_Pager {
|
|
|
|
return &_Pager{
|
2019-06-12 22:55:09 +03:00
|
|
|
reader: r,
|
|
|
|
quit: make(chan struct{}),
|
|
|
|
firstLineOneBased: 1,
|
2019-06-10 22:50:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-12 08:07:13 +03:00
|
|
|
func (p *_Pager) _AddLine(lineNumber int, line string) {
|
|
|
|
for pos, char := range line {
|
|
|
|
p.screen.SetContent(pos, lineNumber, char, nil, tcell.StyleDefault)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *_Pager) _AddLines() {
|
2019-06-11 22:09:57 +03:00
|
|
|
_, height := p.screen.Size()
|
2019-06-12 22:55:09 +03:00
|
|
|
wantedLineCount := height - 1
|
2019-06-11 19:52:38 +03:00
|
|
|
|
2019-06-12 22:55:09 +03:00
|
|
|
lines := p.reader.GetLines(p.firstLineOneBased, wantedLineCount)
|
2019-06-12 08:07:13 +03:00
|
|
|
|
2019-06-12 22:55:09 +03:00
|
|
|
// If we're asking for past-the-end lines, the Reader will clip for us,
|
|
|
|
// and we should adapt to that. Otherwise if you scroll 100 lines past
|
|
|
|
// the end, you'll then have to scroll 100 lines up again before the
|
|
|
|
// display starts scrolling visibly.
|
|
|
|
p.firstLineOneBased = lines.firstLineOneBased
|
|
|
|
|
|
|
|
for screenLineNumber, line := range lines.lines {
|
|
|
|
p._AddLine(screenLineNumber, line)
|
2019-06-11 19:52:38 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-12 08:07:13 +03:00
|
|
|
func (p *_Pager) _AddFooter() {
|
|
|
|
_, height := p.screen.Size()
|
|
|
|
p._AddLine(height-1, "Press ESC / Return / q to exit")
|
|
|
|
}
|
|
|
|
|
2019-06-11 22:09:57 +03:00
|
|
|
func (p *_Pager) _Redraw() {
|
|
|
|
p.screen.Clear()
|
2019-06-11 19:52:38 +03:00
|
|
|
|
2019-06-12 08:07:13 +03:00
|
|
|
p._AddLines()
|
2019-06-11 19:52:38 +03:00
|
|
|
|
2019-06-11 22:09:57 +03:00
|
|
|
p._AddFooter()
|
|
|
|
p.screen.Sync()
|
2019-06-11 19:52:38 +03:00
|
|
|
}
|
|
|
|
|
2019-06-11 22:21:12 +03:00
|
|
|
func (p *_Pager) _Quit() {
|
|
|
|
close(p.quit)
|
|
|
|
}
|
|
|
|
|
2019-06-11 22:28:21 +03:00
|
|
|
func (p *_Pager) _OnKey(key tcell.Key) {
|
|
|
|
switch key {
|
|
|
|
case tcell.KeyEscape, tcell.KeyEnter:
|
|
|
|
p._Quit()
|
2019-06-12 22:55:09 +03:00
|
|
|
|
|
|
|
case tcell.KeyUp:
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased--
|
|
|
|
|
|
|
|
case tcell.KeyDown:
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased++
|
2019-06-13 07:14:41 +03:00
|
|
|
|
|
|
|
case tcell.KeyHome:
|
|
|
|
p.firstLineOneBased = 1
|
|
|
|
|
|
|
|
case tcell.KeyEnd:
|
|
|
|
p.firstLineOneBased = p.reader.LineCount() + 1
|
|
|
|
|
2019-06-11 22:28:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 22:32:24 +03:00
|
|
|
func (p *_Pager) _OnRune(char rune) {
|
|
|
|
switch char {
|
|
|
|
case 'q':
|
|
|
|
p._Quit()
|
2019-06-13 07:14:41 +03:00
|
|
|
case '<', 'g':
|
|
|
|
p.firstLineOneBased = 1
|
|
|
|
case '>', 'G':
|
|
|
|
p.firstLineOneBased = p.reader.LineCount() + 1
|
2019-06-11 22:32:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-10 22:50:31 +03:00
|
|
|
// StartPaging brings up the pager on screen
|
2019-06-11 16:31:26 +03:00
|
|
|
func (p *_Pager) StartPaging() {
|
2019-06-11 19:29:30 +03:00
|
|
|
// This function initially inspired by
|
|
|
|
// https://github.com/gdamore/tcell/blob/master/_demos/unicode.go
|
|
|
|
s, e := tcell.NewScreen()
|
|
|
|
if e != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "%v\n", e)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if e = s.Init(); e != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "%v\n", e)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2019-06-11 22:22:32 +03:00
|
|
|
defer s.Fini()
|
|
|
|
|
2019-06-11 22:21:12 +03:00
|
|
|
p.screen = s
|
2019-06-11 19:29:30 +03:00
|
|
|
|
|
|
|
// Main loop
|
|
|
|
go func() {
|
2019-06-11 19:52:38 +03:00
|
|
|
s.Show()
|
2019-06-11 19:29:30 +03:00
|
|
|
for {
|
2019-06-11 22:09:57 +03:00
|
|
|
p._Redraw()
|
2019-06-11 19:52:38 +03:00
|
|
|
|
2019-06-11 19:29:30 +03:00
|
|
|
ev := s.PollEvent()
|
|
|
|
switch ev := ev.(type) {
|
|
|
|
case *tcell.EventKey:
|
2019-06-11 22:32:24 +03:00
|
|
|
if ev.Key() == tcell.KeyRune {
|
|
|
|
p._OnRune(ev.Rune())
|
|
|
|
} else {
|
|
|
|
p._OnKey(ev.Key())
|
|
|
|
}
|
2019-06-11 22:28:21 +03:00
|
|
|
|
2019-06-11 19:29:30 +03:00
|
|
|
case *tcell.EventResize:
|
2019-06-11 22:28:21 +03:00
|
|
|
// We'll be implicitly redrawn just by taking another lap in the loop
|
2019-06-11 19:29:30 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-06-11 22:21:12 +03:00
|
|
|
<-p.quit
|
2019-06-10 22:50:31 +03:00
|
|
|
}
|