1
1
mirror of https://github.com/walles/moar.git synced 2024-11-27 01:05:23 +03:00
moar/m/pager.go

552 lines
13 KiB
Go
Raw Normal View History

package m
import (
"fmt"
2019-06-29 19:29:37 +03:00
"regexp"
"time"
log "github.com/sirupsen/logrus"
2021-04-15 16:16:06 +03:00
"github.com/walles/moar/twin"
)
2019-07-06 08:45:07 +03:00
type _PagerMode int
const (
2022-05-01 10:16:02 +03:00
_Viewing _PagerMode = iota
_Searching
_NotFound
_GotoLine
2019-07-06 08:45:07 +03:00
)
type StatusBarStyle int
const (
STATUSBAR_STYLE_INVERSE StatusBarStyle = iota
STATUSBAR_STYLE_PLAIN
STATUSBAR_STYLE_BOLD
)
// How do we render unprintable characters?
type UnprintableStyle int
const (
UNPRINTABLE_STYLE_HIGHLIGHT UnprintableStyle = iota
UNPRINTABLE_STYLE_WHITESPACE
)
2021-04-15 16:16:06 +03:00
type eventSpinnerUpdate struct {
spinner string
}
type eventMoreLinesAvailable struct{}
Mandatory line numbers, badly formatted diff --git m/pager.go m/pager.go index 2c2736b..0c7c3a2 100644 --- m/pager.go +++ m/pager.go @@ -22,6 +22,9 @@ const ( _NotFound _PagerMode = 2 ) +// Styling of line numbers +var _NumberStyle = tcell.StyleDefault.Dim(true) + // Pager is the main on-screen pager type Pager struct { reader *Reader @@ -97,17 +100,32 @@ func NewPager(r *Reader) *Pager { } } -func (p *Pager) _AddLine(logger *log.Logger, lineNumber int, line string) { - width, _ := p.screen.Size() - tokens := _CreateScreenLine(logger, lineNumber, p.leftColumnZeroBased, width, line, p.searchPattern) +func (p *Pager) _AddLine(logger *log.Logger, fileLineNumber *int, screenLineNumber int, line string) { + screenWidth, _ := p.screen.Size() + + prefixLength := 0 + lineNumberString := "" + if fileLineNumber != nil { + prefixLength = 3 + lineNumberString = fmt.Sprintf("%*d ", prefixLength-1, *fileLineNumber) + } + + for column, digit := range lineNumberString { + if column >= prefixLength { + break + } + + p.screen.SetContent(column, screenLineNumber, digit, nil, _NumberStyle) + } + + tokens := _CreateScreenLine(logger, p.leftColumnZeroBased, screenWidth-prefixLength, line, p.searchPattern) for column, token := range tokens { - p.screen.SetContent(column, lineNumber, token.Rune, nil, token.Style) + p.screen.SetContent(column+prefixLength, screenLineNumber, token.Rune, nil, token.Style) } } func _CreateScreenLine( logger *log.Logger, - lineNumber int, stringIndexAtColumnZero int, screenColumnsCount int, line string, @@ -183,8 +201,9 @@ func (p *Pager) _AddLines(logger *log.Logger, spinner string) { p.firstLineOneBased = lines.firstLineOneBased screenLineNumber := 0 - for _, line := range lines.lines { - p._AddLine(logger, screenLineNumber, line) + for i, line := range lines.lines { + lineNumber := p.firstLineOneBased + i + p._AddLine(logger, &lineNumber, screenLineNumber, line) screenLineNumber++ } @@ -193,7 +212,7 @@ func (p *Pager) _AddLines(logger *log.Logger, spinner string) { // This happens when we're done eofSpinner = "---" } - p._AddLine(logger, screenLineNumber, _EofMarkerFormat+eofSpinner) + p._AddLine(logger, nil, screenLineNumber, _EofMarkerFormat+eofSpinner) switch p.mode { case _Searching: Change-Id: I2cafedb3e8a87c88564982f42819b16e911c6a1b
2019-11-19 13:12:23 +03:00
// Styling of line numbers
var _numberStyle = twin.StyleDefault.WithAttr(twin.AttrDim)
Mandatory line numbers, badly formatted diff --git m/pager.go m/pager.go index 2c2736b..0c7c3a2 100644 --- m/pager.go +++ m/pager.go @@ -22,6 +22,9 @@ const ( _NotFound _PagerMode = 2 ) +// Styling of line numbers +var _NumberStyle = tcell.StyleDefault.Dim(true) + // Pager is the main on-screen pager type Pager struct { reader *Reader @@ -97,17 +100,32 @@ func NewPager(r *Reader) *Pager { } } -func (p *Pager) _AddLine(logger *log.Logger, lineNumber int, line string) { - width, _ := p.screen.Size() - tokens := _CreateScreenLine(logger, lineNumber, p.leftColumnZeroBased, width, line, p.searchPattern) +func (p *Pager) _AddLine(logger *log.Logger, fileLineNumber *int, screenLineNumber int, line string) { + screenWidth, _ := p.screen.Size() + + prefixLength := 0 + lineNumberString := "" + if fileLineNumber != nil { + prefixLength = 3 + lineNumberString = fmt.Sprintf("%*d ", prefixLength-1, *fileLineNumber) + } + + for column, digit := range lineNumberString { + if column >= prefixLength { + break + } + + p.screen.SetContent(column, screenLineNumber, digit, nil, _NumberStyle) + } + + tokens := _CreateScreenLine(logger, p.leftColumnZeroBased, screenWidth-prefixLength, line, p.searchPattern) for column, token := range tokens { - p.screen.SetContent(column, lineNumber, token.Rune, nil, token.Style) + p.screen.SetContent(column+prefixLength, screenLineNumber, token.Rune, nil, token.Style) } } func _CreateScreenLine( logger *log.Logger, - lineNumber int, stringIndexAtColumnZero int, screenColumnsCount int, line string, @@ -183,8 +201,9 @@ func (p *Pager) _AddLines(logger *log.Logger, spinner string) { p.firstLineOneBased = lines.firstLineOneBased screenLineNumber := 0 - for _, line := range lines.lines { - p._AddLine(logger, screenLineNumber, line) + for i, line := range lines.lines { + lineNumber := p.firstLineOneBased + i + p._AddLine(logger, &lineNumber, screenLineNumber, line) screenLineNumber++ } @@ -193,7 +212,7 @@ func (p *Pager) _AddLines(logger *log.Logger, spinner string) { // This happens when we're done eofSpinner = "---" } - p._AddLine(logger, screenLineNumber, _EofMarkerFormat+eofSpinner) + p._AddLine(logger, nil, screenLineNumber, _EofMarkerFormat+eofSpinner) switch p.mode { case _Searching: Change-Id: I2cafedb3e8a87c88564982f42819b16e911c6a1b
2019-11-19 13:12:23 +03:00
// Pager is the main on-screen pager
2019-10-25 20:24:37 +03:00
type Pager struct {
reader *Reader
2021-04-15 16:16:06 +03:00
screen twin.Screen
2019-07-06 14:33:41 +03:00
quit bool
2022-02-20 09:48:08 +03:00
scrollPosition scrollPosition
2019-07-06 14:33:41 +03:00
leftColumnZeroBased int
mode _PagerMode
searchString string
searchPattern *regexp.Regexp
gotoLineString string
2022-11-27 00:21:02 +03:00
Following bool
isShowingHelp bool
preHelpState *_PreHelpState
// NewPager shows lines by default, this field can hide them
ShowLineNumbers bool
StatusBarStyle StatusBarStyle
ShowStatusBar bool
UnprintableStyle UnprintableStyle
WrapLongLines bool
// Ref: https://github.com/walles/moar/issues/113
QuitIfOneScreen bool
2022-08-07 16:10:33 +03:00
// Ref: https://github.com/walles/moar/issues/94
ScrollLeftHint twin.Cell
ScrollRightHint twin.Cell
SideScrollAmount int // Should be positive
// If true, pager will clear the screen on return. If false, pager will
// clear the last line, and show the cursor.
DeInit bool
}
type _PreHelpState struct {
reader *Reader
2022-02-20 09:48:08 +03:00
scrollPosition scrollPosition
leftColumnZeroBased int
2022-11-27 00:21:02 +03:00
following bool
}
2019-10-16 07:09:21 +03:00
const _EofMarkerFormat = "\x1b[7m" // Reverse video
var _HelpReader = NewReaderFromText("Help", `
2019-07-26 20:15:24 +03:00
Welcome to Moar, the nice pager!
Miscellaneous
-------------
* Press 'q' or 'ESC' to quit
* Press 'w' to toggle wrapping of long lines
* Press '=' to toggle showing the status bar at the bottom
2019-07-26 20:15:24 +03:00
Moving around
-------------
* Arrow keys
* Alt key plus left / right arrow steps one column at a time
* Left / right can be used to hide / show line numbers
* CTRL-p moves to the previous line
* CTRL-n moves to the next line
* 'g' for going to a specific line number
2019-07-26 20:15:24 +03:00
* PageUp / 'b' and PageDown / 'f'
* SPACE moves down a page
2019-07-26 20:15:24 +03:00
* Home and End for start / end of the document
* < to go to the start of the document
* > / 'G' to go to the end of the document
* 'h', 'l' for left and right (as in vim)
* Half page 'u'p / 'd'own, or CTRL-u / CTRL-d
2019-07-26 20:15:24 +03:00
* RETURN moves down one line
Searching
---------
* Type / to start searching, then type what you want to find
* Type RETURN to stop searching
* Find next by typing 'n' (for "next")
* Find previous by typing SHIFT-N or 'p' (for "previous")
* Search is case sensitive if it contains any UPPER CASE CHARACTERS
* Search is interpreted as a regexp if it is a valid one
Reporting bugs
--------------
File issues at https://github.com/walles/moar/issues, or post
questions to johan.walles@gmail.com.
Installing Moar as your default pager
-------------------------------------
2022-04-24 09:29:42 +03:00
Put the following line in your ~/.bashrc, ~/.bash_profile or ~/.zshrc:
export PAGER=moar
2019-07-26 20:15:24 +03:00
Source Code
-----------
Available at https://github.com/walles/moar/.
`)
2022-11-26 23:58:24 +03:00
func (pm _PagerMode) isViewing() bool {
return pm == _Viewing || pm == _NotFound
}
// NewPager creates a new Pager with default settings
2019-10-25 20:24:37 +03:00
func NewPager(r *Reader) *Pager {
2022-02-27 10:15:46 +03:00
var name string
if r == nil || r.name == nil || len(*r.name) == 0 {
name = "Pager"
} else {
name = "Pager " + *r.name
}
2019-10-25 20:24:37 +03:00
return &Pager{
reader: r,
quit: false,
ShowLineNumbers: true,
ShowStatusBar: true,
DeInit: true,
SideScrollAmount: 16,
ScrollLeftHint: twin.NewCell('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
ScrollRightHint: twin.NewCell('>', twin.StyleDefault.WithAttr(twin.AttrReverse)),
scrollPosition: newScrollPosition(name),
}
}
func (p *Pager) setFooter(footer string) {
width, height := p.screen.Size()
2019-06-12 08:07:13 +03:00
pos := 0
var footerStyle twin.Style
2022-12-31 18:54:10 +03:00
if standoutStyle != nil {
footerStyle = *standoutStyle
} else if p.StatusBarStyle == STATUSBAR_STYLE_INVERSE {
footerStyle = twin.StyleDefault.WithAttr(twin.AttrReverse)
} else if p.StatusBarStyle == STATUSBAR_STYLE_PLAIN {
footerStyle = twin.StyleDefault
} else if p.StatusBarStyle == STATUSBAR_STYLE_BOLD {
footerStyle = twin.StyleDefault.WithAttr(twin.AttrBold)
} else {
panic(fmt.Sprint("Unrecognized footer style: ", footerStyle))
}
for _, token := range footer {
p.screen.SetCell(pos, height-1, twin.NewCell(token, footerStyle))
pos++
}
Adaptive width of the line numbers column diff --git m/pager.go m/pager.go index 0c7c3a2..2133057 100644 --- m/pager.go +++ m/pager.go @@ -5,6 +5,7 @@ import ( "log" "os" "regexp" + "strconv" "time" "unicode" "unicode/utf8" @@ -100,13 +101,13 @@ func NewPager(r *Reader) *Pager { } } -func (p *Pager) _AddLine(logger *log.Logger, fileLineNumber *int, screenLineNumber int, line string) { +func (p *Pager) _AddLine(logger *log.Logger, fileLineNumber *int, maxPrefixLength int, screenLineNumber int, line string) { screenWidth, _ := p.screen.Size() prefixLength := 0 lineNumberString := "" if fileLineNumber != nil { - prefixLength = 3 + prefixLength = maxPrefixLength lineNumberString = fmt.Sprintf("%*d ", prefixLength-1, *fileLineNumber) } @@ -200,10 +201,16 @@ func (p *Pager) _AddLines(logger *log.Logger, spinner string) { // display starts scrolling visibly. p.firstLineOneBased = lines.firstLineOneBased + // Count the length of the last line number + // + // Offsets figured out through trial-and-error... + lastLineOneBased := lines.firstLineOneBased + len(lines.lines) - 1 + maxPrefixLength := len(strconv.Itoa(lastLineOneBased)) + 1 + screenLineNumber := 0 for i, line := range lines.lines { lineNumber := p.firstLineOneBased + i - p._AddLine(logger, &lineNumber, screenLineNumber, line) + p._AddLine(logger, &lineNumber, maxPrefixLength, screenLineNumber, line) screenLineNumber++ } @@ -212,7 +219,7 @@ func (p *Pager) _AddLines(logger *log.Logger, spinner string) { // This happens when we're done eofSpinner = "---" } - p._AddLine(logger, nil, screenLineNumber, _EofMarkerFormat+eofSpinner) + p._AddLine(logger, nil, 0, screenLineNumber, _EofMarkerFormat+eofSpinner) switch p.mode { case _Searching: Change-Id: I7ab67a61048557fd11cd9a044dbae5c13264f492
2019-11-19 13:24:01 +03:00
for ; pos < width; pos++ {
p.screen.SetCell(pos, height-1, twin.NewCell(' ', footerStyle))
}
}
2019-10-25 20:24:37 +03:00
// Quit leaves the help screen or quits the pager
func (p *Pager) Quit() {
if !p.isShowingHelp {
p.quit = true
return
}
// Reset help
p.isShowingHelp = false
p.reader = p.preHelpState.reader
2022-02-20 09:48:08 +03:00
p.scrollPosition = p.preHelpState.scrollPosition
p.leftColumnZeroBased = p.preHelpState.leftColumnZeroBased
2022-11-27 00:21:02 +03:00
p.Following = p.preHelpState.following
p.preHelpState = nil
2019-06-11 22:21:12 +03:00
}
func (p *Pager) moveRight(delta int) {
if p.ShowLineNumbers && delta > 0 {
p.ShowLineNumbers = false
return
}
if p.leftColumnZeroBased == 0 && delta < 0 {
p.ShowLineNumbers = true
return
}
Extract sideways scrolling into its own method diff --git m/pager.go m/pager.go index ec81976..0e22e83 100644 --- m/pager.go +++ m/pager.go @@ -41,7 +41,7 @@ type Pager struct { isShowingHelp bool preHelpState *_PreHelpState - lineNumbersWanted bool + showLineNumbers bool } type _PreHelpState struct { @@ -101,7 +101,7 @@ func NewPager(r *Reader) *Pager { reader: r, quit: false, firstLineOneBased: 1, - lineNumbersWanted: true, + showLineNumbers: true, } } @@ -211,7 +211,7 @@ func (p *Pager) _AddLines(logger *log.Logger, spinner string) { lastLineOneBased := lines.firstLineOneBased + len(lines.lines) - 1 maxPrefixLength := len(strconv.Itoa(lastLineOneBased)) + 1 - if !p.lineNumbersWanted { + if !p.showLineNumbers { maxPrefixLength = 0 } @@ -503,6 +503,15 @@ func (p *Pager) _OnSearchKey(logger *log.Logger, key tcell.Key) { } } +func (p *Pager) _MoveRight(delta int) { + result := p.leftColumnZeroBased + delta + if result < 0 { + p.leftColumnZeroBased = 0 + } else { + p.leftColumnZeroBased = result + } +} + func (p *Pager) _OnKey(logger *log.Logger, key tcell.Key) { if p.mode == _Searching { p._OnSearchKey(logger, key) @@ -528,13 +537,10 @@ func (p *Pager) _OnKey(logger *log.Logger, key tcell.Key) { p.firstLineOneBased++ case tcell.KeyRight: - p.leftColumnZeroBased += 16 + p._MoveRight(16) case tcell.KeyLeft: - p.leftColumnZeroBased -= 16 - if p.leftColumnZeroBased < 0 { - p.leftColumnZeroBased = 0 - } + p._MoveRight(-16) case tcell.KeyHome: p.firstLineOneBased = 1 @@ -718,13 +724,10 @@ func (p *Pager) StartPaging(logger *log.Logger, screen tcell.Screen) { p.firstLineOneBased++ case tcell.WheelRight: - p.leftColumnZeroBased += 16 + p._MoveRight(16) case tcell.WheelLeft: - p.leftColumnZeroBased -= 16 - if p.leftColumnZeroBased < 0 { - p.leftColumnZeroBased = 0 - } + p._MoveRight(-16) } case *tcell.EventResize: Change-Id: I5925876d42ec3cd7b8486bb96eb47b81c6855032
2019-11-19 13:38:41 +03:00
result := p.leftColumnZeroBased + delta
if result < 0 {
p.leftColumnZeroBased = 0
} else {
p.leftColumnZeroBased = result
}
}
2022-11-27 00:21:02 +03:00
func (p *Pager) handleScrolledUp() {
p.Following = false
}
func (p *Pager) handleScrolledDown() {
p.Following = p.isScrolledToEnd()
}
func (p *Pager) onKey(keyCode twin.KeyCode) {
2019-07-06 08:45:07 +03:00
if p.mode == _Searching {
p.onSearchKey(keyCode)
return
}
if p.mode == _GotoLine {
p.onGotoLineKey(keyCode)
return
}
2019-07-06 08:54:33 +03:00
if p.mode != _Viewing && p.mode != _NotFound {
panic(fmt.Sprint("Unhandled mode: ", p.mode))
}
2019-07-06 08:45:07 +03:00
// Reset the not-found marker on non-search keypresses
p.mode = _Viewing
2021-04-15 16:16:06 +03:00
switch keyCode {
case twin.KeyEscape:
p.Quit()
2021-04-15 16:16:06 +03:00
case twin.KeyUp:
// Clipping is done in _Redraw()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.PreviousLine(1)
2022-11-27 00:21:02 +03:00
p.handleScrolledUp()
2021-04-15 16:16:06 +03:00
case twin.KeyDown, twin.KeyEnter:
// Clipping is done in _Redraw()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.NextLine(1)
2022-11-27 00:21:02 +03:00
p.handleScrolledDown()
2019-06-13 07:14:41 +03:00
2021-04-15 16:16:06 +03:00
case twin.KeyRight:
p.moveRight(p.SideScrollAmount)
2019-07-06 14:33:41 +03:00
2021-04-15 16:16:06 +03:00
case twin.KeyLeft:
p.moveRight(-p.SideScrollAmount)
2019-07-06 08:45:07 +03:00
case twin.KeyAltRight:
p.moveRight(1)
case twin.KeyAltLeft:
p.moveRight(-1)
2021-04-15 16:16:06 +03:00
case twin.KeyHome:
2022-02-27 10:15:46 +03:00
p.scrollPosition = newScrollPosition("Pager scroll position")
2022-11-27 00:21:02 +03:00
p.handleScrolledUp()
2019-06-13 07:14:41 +03:00
2021-04-15 16:16:06 +03:00
case twin.KeyEnd:
2022-02-20 09:48:08 +03:00
p.scrollToEnd()
2019-06-13 07:14:41 +03:00
2021-04-15 16:16:06 +03:00
case twin.KeyPgUp:
2019-06-13 07:21:43 +03:00
_, height := p.screen.Size()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.PreviousLine(height - 1)
2022-11-27 00:21:02 +03:00
p.handleScrolledUp()
case twin.KeyPgDown:
_, height := p.screen.Size()
p.scrollPosition = p.scrollPosition.NextLine(height - 1)
p.handleScrolledDown()
2019-06-16 11:02:19 +03:00
default:
2021-04-15 16:16:06 +03:00
log.Debugf("Unhandled key event %v", keyCode)
}
}
func (p *Pager) onRune(char rune) {
2019-07-06 08:45:07 +03:00
if p.mode == _Searching {
p.onSearchRune(char)
return
}
if p.mode == _GotoLine {
p.onGotoLineRune(char)
return
}
2019-07-06 08:54:33 +03:00
if p.mode != _Viewing && p.mode != _NotFound {
panic(fmt.Sprint("Unhandled mode: ", p.mode))
}
2019-06-11 22:32:24 +03:00
switch char {
case 'q':
p.Quit()
2019-06-13 07:21:43 +03:00
case '?':
if !p.isShowingHelp {
p.preHelpState = &_PreHelpState{
reader: p.reader,
2022-02-20 09:48:08 +03:00
scrollPosition: p.scrollPosition,
leftColumnZeroBased: p.leftColumnZeroBased,
2022-11-27 00:21:02 +03:00
following: p.Following,
}
p.reader = _HelpReader
2022-02-27 10:15:46 +03:00
p.scrollPosition = newScrollPosition("Pager scroll position")
p.leftColumnZeroBased = 0
2022-11-27 00:21:02 +03:00
p.Following = false
p.isShowingHelp = true
}
case '=':
p.ShowStatusBar = !p.ShowStatusBar
// '\x10' = CTRL-p, should scroll up one line.
// Ref: https://github.com/walles/moar/issues/107#issuecomment-1328354080
case 'k', 'y', '\x10':
// Clipping is done in _Redraw()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.PreviousLine(1)
2022-11-27 00:21:02 +03:00
p.handleScrolledUp()
2019-06-13 16:56:06 +03:00
// '\x0e' = CTRL-n, should scroll down one line.
// Ref: https://github.com/walles/moar/issues/107#issuecomment-1328354080
case 'j', 'e', '\x0e':
// Clipping is done in _Redraw()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.NextLine(1)
2022-11-27 00:21:02 +03:00
p.handleScrolledDown()
2019-06-13 16:56:06 +03:00
case 'l':
// vim right
p.moveRight(p.SideScrollAmount)
case 'h':
// vim left
p.moveRight(-p.SideScrollAmount)
case '<':
2022-02-27 10:15:46 +03:00
p.scrollPosition = newScrollPosition("Pager scroll position")
2022-11-27 00:21:02 +03:00
p.handleScrolledUp()
2019-06-13 07:21:43 +03:00
case '>', 'G':
p.scrollToEnd()
2019-06-13 07:21:43 +03:00
2019-06-13 16:56:06 +03:00
case 'f', ' ':
2019-06-13 07:21:43 +03:00
_, height := p.screen.Size()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.NextLine(height - 1)
2022-11-27 00:21:02 +03:00
p.handleScrolledDown()
2019-06-13 07:21:43 +03:00
case 'b':
_, height := p.screen.Size()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.PreviousLine(height - 1)
2022-11-27 00:21:02 +03:00
p.handleScrolledUp()
2019-06-13 07:21:43 +03:00
// '\x15' = CTRL-u, should work like just 'u'.
// Ref: https://github.com/walles/moar/issues/90
case 'u', '\x15':
2019-07-26 20:15:24 +03:00
_, height := p.screen.Size()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.PreviousLine(height / 2)
2022-11-27 00:21:02 +03:00
p.handleScrolledUp()
2019-07-26 20:15:24 +03:00
// '\x04' = CTRL-d, should work like just 'd'.
// Ref: https://github.com/walles/moar/issues/90
case 'd', '\x04':
2019-07-26 20:15:24 +03:00
_, height := p.screen.Size()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.NextLine(height / 2)
2022-11-27 00:21:02 +03:00
p.handleScrolledDown()
2019-07-26 20:15:24 +03:00
case '/':
2019-07-06 08:45:07 +03:00
p.mode = _Searching
2019-06-30 23:15:27 +03:00
p.searchString = ""
p.searchPattern = nil
case 'g':
p.mode = _GotoLine
p.gotoLineString = ""
2019-07-06 08:45:07 +03:00
case 'n':
p.scrollToNextSearchHit()
2019-07-06 08:45:07 +03:00
2019-07-06 11:31:40 +03:00
case 'p', 'N':
p.scrollToPreviousSearchHit()
2019-07-06 08:45:07 +03:00
case 'w':
p.WrapLongLines = !p.WrapLongLines
2019-06-16 11:02:19 +03:00
default:
log.Debugf("Unhandled rune keypress '%s'/0x%08x", string(char), int32(char))
2019-06-11 22:32:24 +03:00
}
}
// StartPaging brings up the pager on screen
2021-04-15 16:16:06 +03:00
func (p *Pager) StartPaging(screen twin.Screen) {
unprintableStyle = p.UnprintableStyle
2022-12-31 18:41:57 +03:00
ConsumeLessTermcapEnvs()
p.screen = screen
go func() {
for {
2021-04-15 16:16:06 +03:00
// Wait for new lines to appear...
<-p.reader.moreLinesAdded
2021-04-15 16:16:06 +03:00
// ... and notify the main loop so it can show them:
screen.Events() <- eventMoreLinesAvailable{}
// Delay updates a bit so that we don't waste time refreshing
// the screen too often.
//
// Note that the delay is *after* reacting, this way single-line
// updates are reacted to immediately, and the first output line
// read will appear on screen without delay.
time.Sleep(200 * time.Millisecond)
}
}()
go func() {
2021-04-15 16:16:06 +03:00
// Spin the spinner as long as contents is still loading
2019-10-30 00:05:42 +03:00
spinnerFrames := [...]string{"/.\\", "-o-", "\\O/", "| |"}
spinnerIndex := 0
for {
if p.reader.done.Load() {
break
}
2021-04-15 16:16:06 +03:00
screen.Events() <- eventSpinnerUpdate{spinnerFrames[spinnerIndex]}
spinnerIndex++
if spinnerIndex >= len(spinnerFrames) {
spinnerIndex = 0
}
time.Sleep(200 * time.Millisecond)
}
// Empty our spinner, loading done!
2021-04-15 16:16:06 +03:00
screen.Events() <- eventSpinnerUpdate{""}
}()
// Main loop
spinner := ""
2019-06-15 10:23:53 +03:00
for !p.quit {
2021-04-15 16:16:06 +03:00
if len(screen.Events()) == 0 {
// Nothing more to process for now
// Ref:
// https://github.com/gwsw/less/blob/ff8869aa0485f7188d942723c9fb50afb1892e62/command.c#L828-L831
if FIXME.entire_file_displayed() && p.QuitIfOneScreen && !p.isShowingHelp {
// Ref:
// https://github.com/walles/moar/issues/113#issuecomment-1368294132
p.ShowLineNumbers = false
p.DeInit = false
p.quit = true
break
}
// Redraw the screen!
p.redraw(spinner)
2021-04-15 16:16:06 +03:00
}
event := <-screen.Events()
switch event := event.(type) {
case twin.EventKeyCode:
log.Tracef("Handling key event %d...", event.KeyCode())
p.onKey(event.KeyCode())
2021-04-15 16:16:06 +03:00
case twin.EventRune:
log.Tracef("Handling rune event '%c'/0x%04x...", event.Rune(), event.Rune())
p.onRune(event.Rune())
2021-04-15 16:16:06 +03:00
case twin.EventMouse:
log.Tracef("Handling mouse event %d...", event.Buttons())
2021-04-15 16:16:06 +03:00
switch event.Buttons() {
case twin.MouseWheelUp:
// Clipping is done in _Redraw()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.PreviousLine(1)
2021-04-15 16:16:06 +03:00
case twin.MouseWheelDown:
// Clipping is done in _Redraw()
2022-02-26 10:35:29 +03:00
p.scrollPosition = p.scrollPosition.NextLine(1)
2021-04-15 16:16:06 +03:00
case twin.MouseWheelLeft:
p.moveRight(-p.SideScrollAmount)
2021-04-15 16:16:06 +03:00
case twin.MouseWheelRight:
p.moveRight(p.SideScrollAmount)
}
2021-04-15 16:16:06 +03:00
case twin.EventResize:
2019-06-15 10:23:53 +03:00
// We'll be implicitly redrawn just by taking another lap in the loop
2019-06-16 11:02:19 +03:00
2021-04-15 16:16:06 +03:00
case eventMoreLinesAvailable:
if p.mode.isViewing() && p.Following {
2022-11-26 23:58:24 +03:00
p.scrollToEnd()
}
2021-04-15 16:16:06 +03:00
case eventSpinnerUpdate:
spinner = event.spinner
2019-06-16 11:02:19 +03:00
default:
2021-04-15 16:16:06 +03:00
log.Warnf("Unhandled event type: %v", event)
}
2019-06-15 10:23:53 +03:00
}
2019-07-11 19:34:10 +03:00
if p.reader.err != nil {
log.Warnf("Reader reported an error: %s", p.reader.err.Error())
}
}
// After the pager has exited and the normal screen has been restored, you can
// call this method to print the pager contents to screen again, faking
// "leaving" pager contents on screen after exit.
func (p *Pager) ReprintAfterExit() error {
// Figure out how many screen lines are used by pager contents
2022-02-20 09:48:08 +03:00
2022-02-26 19:01:15 +03:00
renderedScreenLines, _ := p.renderScreenLines()
screenLinesCount := len(renderedScreenLines)
2022-02-20 09:48:08 +03:00
2022-02-26 19:01:15 +03:00
_, screenHeight := p.screen.Size()
screenHeightWithoutFooter := screenHeight - 1
if screenLinesCount > screenHeightWithoutFooter {
screenLinesCount = screenHeightWithoutFooter
}
2022-02-26 19:01:15 +03:00
if screenLinesCount > 0 {
p.screen.ShowNLines(screenLinesCount)
}
return nil
}