From 0c39d63aa04206a2a60c8eecf8ccca2ccc44ed5c Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Sat, 26 Nov 2022 21:58:24 +0100 Subject: [PATCH 1/5] Initial attempt --- m/pager.go | 7 +++++++ m/scrollPosition.go | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/m/pager.go b/m/pager.go index 23c4dc4a..6e09e584 100644 --- a/m/pager.go +++ b/m/pager.go @@ -133,6 +133,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 @@ -460,6 +464,9 @@ func (p *Pager) StartPaging(screen twin.Screen) { case eventMoreLinesAvailable: // Doing nothing here is fine; screen will be refreshed on the next // iteration of the main loop. + if p.mode.isViewing() && p.scrollPosition.HasScrolledDown() && p.wasScrolledToEnd() { + p.scrollToEnd() + } case eventSpinnerUpdate: spinner = event.spinner diff --git a/m/scrollPosition.go b/m/scrollPosition.go index dc72f27d..206720d7 100644 --- a/m/scrollPosition.go +++ b/m/scrollPosition.go @@ -56,6 +56,10 @@ func canonicalFromPager(pager *Pager) scrollPositionCanonical { } } +func (s *scrollPosition) HasScrolledDown() bool { + return s.internalDontTouch.lineNumberOneBased > 1 || s.internalDontTouch.deltaScreenLines > 0 +} + // Create a new position, scrolled towards the end of the file func (s scrollPosition) PreviousLine(scrollDistance int) scrollPosition { return scrollPosition{ From 54ec5d690fe291e6dc49627504b29df47f0f58b0 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Sat, 26 Nov 2022 22:21:02 +0100 Subject: [PATCH 2/5] Make following work --- m/pager.go | 35 ++++++++++++++++++++++++++++++----- m/scrollPosition.go | 2 ++ m/search.go | 6 ++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/m/pager.go b/m/pager.go index 6e09e584..55431b3d 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 @@ -193,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 } @@ -215,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) @@ -238,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) @@ -251,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) @@ -291,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 } @@ -304,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 @@ -319,6 +339,7 @@ func (p *Pager) onRune(char rune) { case '<': p.scrollPosition = newScrollPosition("Pager scroll position") + p.handleScrolledUp() case '>', 'G': p.scrollToEnd() @@ -326,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 @@ -464,7 +489,7 @@ func (p *Pager) StartPaging(screen twin.Screen) { case eventMoreLinesAvailable: // Doing nothing here is fine; screen will be refreshed on the next // iteration of the main loop. - if p.mode.isViewing() && p.scrollPosition.HasScrolledDown() && p.wasScrolledToEnd() { + if p.mode.isViewing() && p.scrollPosition.HasScrolledDown() && p.Following { p.scrollToEnd() } diff --git a/m/scrollPosition.go b/m/scrollPosition.go index 206720d7..8f254edd 100644 --- a/m/scrollPosition.go +++ b/m/scrollPosition.go @@ -281,6 +281,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() { From fed3bbfbb8f29f1a8a7089a0ef8d9780386c977d Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Sat, 26 Nov 2022 22:23:40 +0100 Subject: [PATCH 3/5] Follow after first down Even if it didn't scroll down right now. --- m/pager.go | 7 ++++--- m/scrollPosition.go | 4 ---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/m/pager.go b/m/pager.go index 55431b3d..1a1b7904 100644 --- a/m/pager.go +++ b/m/pager.go @@ -487,10 +487,11 @@ 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.scrollPosition.HasScrolledDown() && p.Following { + if p.mode.isViewing() && p.Following { p.scrollToEnd() + } else { + // Doing nothing here is fine; screen will be refreshed on the next + // iteration of the main loop. } case eventSpinnerUpdate: diff --git a/m/scrollPosition.go b/m/scrollPosition.go index 8f254edd..403b41cd 100644 --- a/m/scrollPosition.go +++ b/m/scrollPosition.go @@ -56,10 +56,6 @@ func canonicalFromPager(pager *Pager) scrollPositionCanonical { } } -func (s *scrollPosition) HasScrolledDown() bool { - return s.internalDontTouch.lineNumberOneBased > 1 || s.internalDontTouch.deltaScreenLines > 0 -} - // Create a new position, scrolled towards the end of the file func (s scrollPosition) PreviousLine(scrollDistance int) scrollPosition { return scrollPosition{ From e06acc6243935370c8accb62930aa175cfd2ff23 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Sat, 26 Nov 2022 22:26:26 +0100 Subject: [PATCH 4/5] Document following --- README.md | 1 + 1 file changed, 1 insertion(+) 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: From bb1126bf7ae900386124240e40f200796424a472 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Sat, 26 Nov 2022 22:28:38 +0100 Subject: [PATCH 5/5] Remove unclear comment That I wrote! --- m/pager.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/m/pager.go b/m/pager.go index 1a1b7904..bacc6f1e 100644 --- a/m/pager.go +++ b/m/pager.go @@ -489,9 +489,6 @@ func (p *Pager) StartPaging(screen twin.Screen) { case eventMoreLinesAvailable: if p.mode.isViewing() && p.Following { p.scrollToEnd() - } else { - // Doing nothing here is fine; screen will be refreshed on the next - // iteration of the main loop. } case eventSpinnerUpdate: