diff --git a/README.md b/README.md index 15dc01e2..ed9bc5bf 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Doing the right thing includes: - The position in the file is always shown - Supports **word wrapping** (on actual word boundaries) if requested using `--wrap` or by pressing w +- **Follows output** as long as you are on the last line, just like `tail -f` [For compatibility reasons](https://github.com/walles/moar/issues/14), `moar` uses the formats declared in these environment variables when viewing man pages: diff --git a/m/pager.go b/m/pager.go index 23c4dc4a..bacc6f1e 100644 --- a/m/pager.go +++ b/m/pager.go @@ -55,6 +55,7 @@ type Pager struct { searchString string searchPattern *regexp.Regexp gotoLineString string + Following bool isShowingHelp bool preHelpState *_PreHelpState @@ -82,6 +83,7 @@ type _PreHelpState struct { reader *Reader scrollPosition scrollPosition leftColumnZeroBased int + following bool } const _EofMarkerFormat = "\x1b[7m" // Reverse video @@ -133,6 +135,10 @@ Source Code Available at https://github.com/walles/moar/. `) +func (pm _PagerMode) isViewing() bool { + return pm == _Viewing || pm == _NotFound +} + // NewPager creates a new Pager func NewPager(r *Reader) *Pager { var name string @@ -189,6 +195,7 @@ func (p *Pager) Quit() { p.reader = p.preHelpState.reader p.scrollPosition = p.preHelpState.scrollPosition p.leftColumnZeroBased = p.preHelpState.leftColumnZeroBased + p.Following = p.preHelpState.following p.preHelpState = nil } @@ -211,6 +218,14 @@ func (p *Pager) moveRight(delta int) { } } +func (p *Pager) handleScrolledUp() { + p.Following = false +} + +func (p *Pager) handleScrolledDown() { + p.Following = p.isScrolledToEnd() +} + func (p *Pager) onKey(keyCode twin.KeyCode) { if p.mode == _Searching { p.onSearchKey(keyCode) @@ -234,10 +249,12 @@ func (p *Pager) onKey(keyCode twin.KeyCode) { case twin.KeyUp: // Clipping is done in _Redraw() p.scrollPosition = p.scrollPosition.PreviousLine(1) + p.handleScrolledUp() case twin.KeyDown, twin.KeyEnter: // Clipping is done in _Redraw() p.scrollPosition = p.scrollPosition.NextLine(1) + p.handleScrolledDown() case twin.KeyRight: p.moveRight(16) @@ -247,17 +264,20 @@ func (p *Pager) onKey(keyCode twin.KeyCode) { case twin.KeyHome: p.scrollPosition = newScrollPosition("Pager scroll position") + p.handleScrolledUp() case twin.KeyEnd: p.scrollToEnd() - case twin.KeyPgDown: - _, height := p.screen.Size() - p.scrollPosition = p.scrollPosition.NextLine(height - 1) - case twin.KeyPgUp: _, height := p.screen.Size() p.scrollPosition = p.scrollPosition.PreviousLine(height - 1) + p.handleScrolledUp() + + case twin.KeyPgDown: + _, height := p.screen.Size() + p.scrollPosition = p.scrollPosition.NextLine(height - 1) + p.handleScrolledDown() default: log.Debugf("Unhandled key event %v", keyCode) @@ -287,10 +307,12 @@ func (p *Pager) onRune(char rune) { reader: p.reader, scrollPosition: p.scrollPosition, leftColumnZeroBased: p.leftColumnZeroBased, + following: p.Following, } p.reader = _HelpReader p.scrollPosition = newScrollPosition("Pager scroll position") p.leftColumnZeroBased = 0 + p.Following = false p.isShowingHelp = true } @@ -300,10 +322,12 @@ func (p *Pager) onRune(char rune) { case 'k', 'y': // Clipping is done in _Redraw() p.scrollPosition = p.scrollPosition.PreviousLine(1) + p.handleScrolledUp() case 'j', 'e': // Clipping is done in _Redraw() p.scrollPosition = p.scrollPosition.NextLine(1) + p.handleScrolledDown() case 'l': // vim right @@ -315,6 +339,7 @@ func (p *Pager) onRune(char rune) { case '<': p.scrollPosition = newScrollPosition("Pager scroll position") + p.handleScrolledUp() case '>', 'G': p.scrollToEnd() @@ -322,22 +347,26 @@ func (p *Pager) onRune(char rune) { case 'f', ' ': _, height := p.screen.Size() p.scrollPosition = p.scrollPosition.NextLine(height - 1) + p.handleScrolledDown() case 'b': _, height := p.screen.Size() p.scrollPosition = p.scrollPosition.PreviousLine(height - 1) + p.handleScrolledUp() // '\x15' = CTRL-u, should work like just 'u'. // Ref: https://github.com/walles/moar/issues/90 case 'u', '\x15': _, height := p.screen.Size() p.scrollPosition = p.scrollPosition.PreviousLine(height / 2) + p.handleScrolledUp() // '\x04' = CTRL-d, should work like just 'd'. // Ref: https://github.com/walles/moar/issues/90 case 'd', '\x04': _, height := p.screen.Size() p.scrollPosition = p.scrollPosition.NextLine(height / 2) + p.handleScrolledDown() case '/': p.mode = _Searching @@ -458,8 +487,9 @@ func (p *Pager) StartPaging(screen twin.Screen) { // We'll be implicitly redrawn just by taking another lap in the loop case eventMoreLinesAvailable: - // Doing nothing here is fine; screen will be refreshed on the next - // iteration of the main loop. + if p.mode.isViewing() && p.Following { + p.scrollToEnd() + } case eventSpinnerUpdate: spinner = event.spinner diff --git a/m/scrollPosition.go b/m/scrollPosition.go index dc72f27d..403b41cd 100644 --- a/m/scrollPosition.go +++ b/m/scrollPosition.go @@ -277,6 +277,8 @@ func (p *Pager) scrollToEnd() { // Scroll down enough. We know for sure the last line won't wrap into more // lines than the number of characters it contains. p.scrollPosition.internalDontTouch.deltaScreenLines = len(lastInputLine.raw) + + p.Following = true } // Can be either because Pager.scrollToEnd() was just called or because the user diff --git a/m/search.go b/m/search.go index 902cd564..fd4e8d58 100644 --- a/m/search.go +++ b/m/search.go @@ -115,6 +115,9 @@ func (p *Pager) scrollToNextSearchHit() { return } p.scrollPosition = *firstHitPosition + + // Don't let any search hit scroll out of sight + p.Following = false } func (p *Pager) scrollToPreviousSearchHit() { @@ -150,6 +153,9 @@ func (p *Pager) scrollToPreviousSearchHit() { return } p.scrollPosition = *firstHitPosition + + // Don't let any search hit scroll out of sight + p.Following = false } func (p *Pager) updateSearchPattern() {