From 4bd2d70a53aa365a374443ccf2bc95abaadc3ee3 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Wed, 27 Sep 2023 20:23:27 +0200 Subject: [PATCH] Add support for +1234 command line option Fixes #155. --- m/pager.go | 48 +++++++++++++++++++++++++++++++-------------- m/scrollPosition.go | 13 ++++++++++-- m/search.go | 4 ++-- moar.1 | 4 ++++ moar.go | 48 +++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 94 insertions(+), 23 deletions(-) diff --git a/m/pager.go b/m/pager.go index a44aaac..2782b55 100644 --- a/m/pager.go +++ b/m/pager.go @@ -2,6 +2,7 @@ package m import ( "fmt" + "math" "regexp" "time" @@ -59,7 +60,9 @@ type Pager struct { searchString string searchPattern *regexp.Regexp gotoLineString string - Following bool + + // We used to have a "Following" field here. If you want to follow, set + // TargetLineNumberOneBased to math.MaxInt instead, see below. isShowingHelp bool preHelpState *_PreHelpState @@ -83,16 +86,20 @@ type Pager struct { SideScrollAmount int // Should be positive + // If non-zero, scroll to this line number as soon as possible. Set to + // math.MaxInt to follow the end of the input (tail). + TargetLineNumberOneBased int + // 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 - scrollPosition scrollPosition - leftColumnZeroBased int - following bool + reader *Reader + scrollPosition scrollPosition + leftColumnZeroBased int + targetLineNumberOneBased int } const _EofMarkerFormat = "\x1b[7m" // Reverse video @@ -210,7 +217,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.TargetLineNumberOneBased = p.preHelpState.targetLineNumberOneBased p.preHelpState = nil } @@ -235,11 +242,15 @@ func (p *Pager) moveRight(delta int) { } func (p *Pager) handleScrolledUp() { - p.Following = false + p.TargetLineNumberOneBased = 0 } func (p *Pager) handleScrolledDown() { - p.Following = p.isScrolledToEnd() + if p.isScrolledToEnd() { + p.TargetLineNumberOneBased = math.MaxInt + } else { + p.TargetLineNumberOneBased = 0 + } } func (p *Pager) onKey(keyCode twin.KeyCode) { @@ -326,15 +337,15 @@ func (p *Pager) onRune(char rune) { case '?': if !p.isShowingHelp { p.preHelpState = &_PreHelpState{ - reader: p.reader, - scrollPosition: p.scrollPosition, - leftColumnZeroBased: p.leftColumnZeroBased, - following: p.Following, + reader: p.reader, + scrollPosition: p.scrollPosition, + leftColumnZeroBased: p.leftColumnZeroBased, + targetLineNumberOneBased: p.TargetLineNumberOneBased, } p.reader = _HelpReader p.scrollPosition = newScrollPosition("Pager scroll position") p.leftColumnZeroBased = 0 - p.Following = false + p.TargetLineNumberOneBased = 0 p.isShowingHelp = true } @@ -539,8 +550,15 @@ func (p *Pager) StartPaging(screen twin.Screen) { return case eventMoreLinesAvailable: - if p.mode.isViewing() && p.Following { - p.scrollToEnd() + if p.mode.isViewing() && p.TargetLineNumberOneBased > 0 { + // The user wants to scroll down to a specific line number + if p.reader.GetLineCount() >= p.TargetLineNumberOneBased { + p.scrollPosition = NewScrollPositionFromLineNumberOneBased(p.TargetLineNumberOneBased, "goToTargetLineNumber") + p.TargetLineNumberOneBased = 0 + } else { + // Not there yet, keep scrolling + p.scrollToEnd() + } } case eventMaybeDone: diff --git a/m/scrollPosition.go b/m/scrollPosition.go index 362a89a..1dee6ed 100644 --- a/m/scrollPosition.go +++ b/m/scrollPosition.go @@ -1,6 +1,9 @@ package m -import "fmt" +import ( + "fmt" + "math" +) // Please create using newScrollPosition(name) type scrollPosition struct { @@ -305,7 +308,13 @@ func (p *Pager) scrollToEnd() { // lines than the number of characters it contains. p.scrollPosition.internalDontTouch.deltaScreenLines = len(lastInputLine.raw) - p.Following = true + if p.TargetLineNumberOneBased == 0 { + // Start following the end of the file + // + // Otherwise, if we're already aiming for some place, don't overwrite + // that. + p.TargetLineNumberOneBased = math.MaxInt + } } // Can be either because Pager.scrollToEnd() was just called or because the user diff --git a/m/search.go b/m/search.go index 35ac66d..ff21035 100644 --- a/m/search.go +++ b/m/search.go @@ -117,7 +117,7 @@ func (p *Pager) scrollToNextSearchHit() { p.scrollPosition = *firstHitPosition // Don't let any search hit scroll out of sight - p.Following = false + p.TargetLineNumberOneBased = 0 } func (p *Pager) scrollToPreviousSearchHit() { @@ -155,7 +155,7 @@ func (p *Pager) scrollToPreviousSearchHit() { p.scrollPosition = *firstHitPosition // Don't let any search hit scroll out of sight - p.Following = false + p.TargetLineNumberOneBased = 0 } func (p *Pager) updateSearchPattern() { diff --git a/moar.1 b/moar.1 index 016ced5..eb88e59 100644 --- a/moar.1 +++ b/moar.1 @@ -95,6 +95,10 @@ Print trace logs after exiting, more verbose than \fB\-\-wrap\fR Wrap long lines, toggle with .B w +.TP +\fB\+\1234\fR +Immediately scroll to line +.B 1234 .SH ENVIRONMENT Having .B PAGER=moar diff --git a/moar.go b/moar.go index 6449435..0e88da6 100644 --- a/moar.go +++ b/moar.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "io" + "math" "os" "os/exec" "path/filepath" @@ -80,6 +81,9 @@ func printUsage(output io.Writer, flagSet *flag.FlagSet, printCommandline bool) _, _ = fmt.Fprintln(output, "Options:") flagSet.PrintDefaults() + + _, _ = fmt.Fprintln(output, " +1234") + _, _ = fmt.Fprintln(output, " \tImmediately scroll to line 1234") } // "moar" if we're in the $PATH, otherwise an absolute path @@ -271,6 +275,35 @@ func tryOpen(filename string) error { return err } +// Parses an argument like "+123" anywhere on the command line into a one-based +// line number, and returns the remaining args. +// +// Returns 0 on no target line number specified. +func getTargetLineNumberOneBased(flagSet *flag.FlagSet) (int, []string) { + args := flagSet.Args() + for i, arg := range args { + if !strings.HasPrefix(arg, "+") { + continue + } + + lineNumber, err := strconv.ParseInt(arg[1:], 10, 32) + if err != nil { + continue + } + + // Remove the target line number from the args + // + // Ref: https://stackoverflow.com/a/57213476/473672 + remainingArgs := make([]string, 0) + remainingArgs = append(remainingArgs, args[:i]...) + remainingArgs = append(remainingArgs, args[i+1:]...) + + return int(lineNumber), remainingArgs + } + + return 0, args +} + func main() { // FIXME: If we get a CTRL-C, get terminal back into a useful state before terminating @@ -370,8 +403,10 @@ func main() { TimestampFormat: time.StampMicro, }) - if len(flagSet.Args()) > 1 { - fmt.Fprintln(os.Stderr, "ERROR: Expected exactly one filename, or data piped from stdin, got:", flagSet.Args()) + targetLineNumberOneBased, remainingArgs := getTargetLineNumberOneBased(flagSet) + + if len(remainingArgs) > 1 { + fmt.Fprintln(os.Stderr, "ERROR: Expected exactly one filename, or data piped from stdin, got:", remainingArgs) fmt.Fprintln(os.Stderr) printUsage(os.Stderr, flagSet, true) @@ -381,7 +416,7 @@ func main() { stdinIsRedirected := !term.IsTerminal(int(os.Stdin.Fd())) stdoutIsRedirected := !term.IsTerminal(int(os.Stdout.Fd())) var inputFilename *string - if len(flagSet.Args()) == 1 { + if len(remainingArgs) == 1 { word := flagSet.Arg(0) inputFilename = &word @@ -444,7 +479,6 @@ func main() { pager := m.NewPager(reader) pager.WrapLongLines = *wrap - pager.Following = *follow pager.ShowLineNumbers = !*noLineNumbers pager.ShowStatusBar = !*noStatusBar pager.DeInit = !*noClearOnExit @@ -454,6 +488,12 @@ func main() { pager.ScrollLeftHint = *scrollLeftHint pager.ScrollRightHint = *scrollRightHint pager.SideScrollAmount = int(*shift) + + pager.TargetLineNumberOneBased = targetLineNumberOneBased + if *follow && pager.TargetLineNumberOneBased == 0 { + pager.TargetLineNumberOneBased = math.MaxInt + } + startPaging(pager, screen) }