2019-06-10 22:50:31 +03:00
|
|
|
package m
|
|
|
|
|
2019-06-11 19:29:30 +03:00
|
|
|
import (
|
|
|
|
"fmt"
|
2019-06-29 19:29:37 +03:00
|
|
|
"regexp"
|
2019-07-15 00:18:26 +03:00
|
|
|
"time"
|
2019-07-06 23:16:08 +03:00
|
|
|
"unicode"
|
2019-11-02 14:17:38 +03:00
|
|
|
"unicode/utf8"
|
2019-06-11 19:29:30 +03:00
|
|
|
|
2020-09-05 21:31:21 +03:00
|
|
|
log "github.com/sirupsen/logrus"
|
2021-04-15 16:16:06 +03:00
|
|
|
"github.com/walles/moar/twin"
|
2019-06-11 19:29:30 +03:00
|
|
|
)
|
|
|
|
|
2019-07-06 08:45:07 +03:00
|
|
|
type _PagerMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
_Viewing _PagerMode = 0
|
|
|
|
_Searching _PagerMode = 1
|
|
|
|
_NotFound _PagerMode = 2
|
|
|
|
)
|
|
|
|
|
2022-02-18 21:09:05 +03:00
|
|
|
type StatusBarStyle int
|
|
|
|
|
|
|
|
const (
|
|
|
|
STATUSBAR_STYLE_INVERSE StatusBarStyle = iota
|
|
|
|
STATUSBAR_STYLE_PLAIN
|
|
|
|
STATUSBAR_STYLE_BOLD
|
|
|
|
)
|
|
|
|
|
2022-02-27 11:39:16 +03:00
|
|
|
// 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
|
2021-05-21 20:29:22 +03:00
|
|
|
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
|
|
|
|
2019-06-10 22:50:31 +03:00
|
|
|
// Pager is the main on-screen pager
|
2019-10-25 20:24:37 +03:00
|
|
|
type Pager struct {
|
2019-07-09 20:53:04 +03:00
|
|
|
reader *Reader
|
2021-04-15 16:16:06 +03:00
|
|
|
screen twin.Screen
|
2019-07-06 14:33:41 +03:00
|
|
|
quit bool
|
|
|
|
firstLineOneBased int
|
|
|
|
leftColumnZeroBased int
|
2019-06-28 23:41:29 +03:00
|
|
|
|
2019-07-06 08:45:07 +03:00
|
|
|
mode _PagerMode
|
2019-06-29 19:29:37 +03:00
|
|
|
searchString string
|
|
|
|
searchPattern *regexp.Regexp
|
2019-07-26 10:26:58 +03:00
|
|
|
|
|
|
|
isShowingHelp bool
|
|
|
|
preHelpState *_PreHelpState
|
Make line number configurable through code
diff --git m/pager.go m/pager.go
index 2133057..ec81976 100644
--- m/pager.go
+++ m/pager.go
@@ -40,6 +40,8 @@ type Pager struct {
isShowingHelp bool
preHelpState *_PreHelpState
+
+ lineNumbersWanted bool
}
type _PreHelpState struct {
@@ -60,6 +62,7 @@ Quitting
Moving around
-------------
* Arrow keys
+* Left / right can be used to hide / show line numbers
* PageUp / 'b' and PageDown / 'f'
* Half page 'u'p / 'd'own
* Home and End for start / end of the document
@@ -98,6 +101,7 @@ func NewPager(r *Reader) *Pager {
reader: r,
quit: false,
firstLineOneBased: 1,
+ lineNumbersWanted: true,
}
}
@@ -106,7 +110,7 @@ func (p *Pager) _AddLine(logger *log.Logger, fileLineNumber *int, maxPrefixLengt
prefixLength := 0
lineNumberString := ""
- if fileLineNumber != nil {
+ if maxPrefixLength > 0 && fileLineNumber != nil {
prefixLength = maxPrefixLength
lineNumberString = fmt.Sprintf("%*d ", prefixLength-1, *fileLineNumber)
}
@@ -207,6 +211,10 @@ 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 {
+ maxPrefixLength = 0
+ }
+
screenLineNumber := 0
for i, line := range lines.lines {
lineNumber := p.firstLineOneBased + i
Change-Id: I79cd0d46e8590cfdcd9e0ee942eeb0098676d25d
2019-11-19 13:28:28 +03:00
|
|
|
|
2021-04-10 08:12:21 +03:00
|
|
|
// NewPager shows lines by default, this field can hide them
|
|
|
|
ShowLineNumbers bool
|
m: make Page a method
Page is a great function, but it is limited, because it makes too many choices
for the user. The global DeInit was a good start, but really that shouldve always
been a Pager field.
Move DeInit inside Pager, and set default to true as before in the NewPager
function. Then, users can call Page, but still have control over DeInit, as well
as Line Numbers, and not have to worry about creating a Screen. Example code:
r := m.NewReaderFromText("moar", strings.Repeat("moar\n", 99))
p := m.NewPager(r)
p.DeInit, p.ShowLineNumbers = false, false
p.Page()
2021-04-12 05:55:03 +03:00
|
|
|
|
2022-02-27 11:39:16 +03:00
|
|
|
StatusBarStyle StatusBarStyle
|
|
|
|
UnprintableStyle UnprintableStyle
|
2022-02-18 21:09:05 +03:00
|
|
|
|
2021-05-22 16:32:49 +03:00
|
|
|
WrapLongLines bool
|
|
|
|
|
m: make Page a method
Page is a great function, but it is limited, because it makes too many choices
for the user. The global DeInit was a good start, but really that shouldve always
been a Pager field.
Move DeInit inside Pager, and set default to true as before in the NewPager
function. Then, users can call Page, but still have control over DeInit, as well
as Line Numbers, and not have to worry about creating a Screen. Example code:
r := m.NewReaderFromText("moar", strings.Repeat("moar\n", 99))
p := m.NewPager(r)
p.DeInit, p.ShowLineNumbers = false, false
p.Page()
2021-04-12 05:55:03 +03:00
|
|
|
// If true, pager will clear the screen on return. If false, pager will
|
|
|
|
// clear the last line, and show the cursor.
|
|
|
|
DeInit bool
|
2019-07-26 10:26:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type _PreHelpState struct {
|
|
|
|
reader *Reader
|
|
|
|
firstLineOneBased int
|
|
|
|
leftColumnZeroBased int
|
2019-06-10 22:50:31 +03:00
|
|
|
}
|
|
|
|
|
2019-10-16 07:09:21 +03:00
|
|
|
const _EofMarkerFormat = "\x1b[7m" // Reverse video
|
2019-08-04 08:15:35 +03:00
|
|
|
|
2019-07-26 20:07:51 +03:00
|
|
|
var _HelpReader = NewReaderFromText("Help", `
|
2019-07-26 20:15:24 +03:00
|
|
|
Welcome to Moar, the nice pager!
|
|
|
|
|
2021-05-22 16:32:49 +03:00
|
|
|
Miscellaneous
|
|
|
|
-------------
|
2019-07-26 20:15:24 +03:00
|
|
|
* Press 'q' or ESC to quit
|
2021-05-22 16:32:49 +03:00
|
|
|
* Press 'w' to toggle wrapping of long lines
|
2019-07-26 20:15:24 +03:00
|
|
|
|
|
|
|
Moving around
|
|
|
|
-------------
|
|
|
|
* Arrow keys
|
2020-09-05 21:31:21 +03:00
|
|
|
* 'h', 'l' for left and right (as in vim)
|
Make line number configurable through code
diff --git m/pager.go m/pager.go
index 2133057..ec81976 100644
--- m/pager.go
+++ m/pager.go
@@ -40,6 +40,8 @@ type Pager struct {
isShowingHelp bool
preHelpState *_PreHelpState
+
+ lineNumbersWanted bool
}
type _PreHelpState struct {
@@ -60,6 +62,7 @@ Quitting
Moving around
-------------
* Arrow keys
+* Left / right can be used to hide / show line numbers
* PageUp / 'b' and PageDown / 'f'
* Half page 'u'p / 'd'own
* Home and End for start / end of the document
@@ -98,6 +101,7 @@ func NewPager(r *Reader) *Pager {
reader: r,
quit: false,
firstLineOneBased: 1,
+ lineNumbersWanted: true,
}
}
@@ -106,7 +110,7 @@ func (p *Pager) _AddLine(logger *log.Logger, fileLineNumber *int, maxPrefixLengt
prefixLength := 0
lineNumberString := ""
- if fileLineNumber != nil {
+ if maxPrefixLength > 0 && fileLineNumber != nil {
prefixLength = maxPrefixLength
lineNumberString = fmt.Sprintf("%*d ", prefixLength-1, *fileLineNumber)
}
@@ -207,6 +211,10 @@ 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 {
+ maxPrefixLength = 0
+ }
+
screenLineNumber := 0
for i, line := range lines.lines {
lineNumber := p.firstLineOneBased + i
Change-Id: I79cd0d46e8590cfdcd9e0ee942eeb0098676d25d
2019-11-19 13:28:28 +03:00
|
|
|
* Left / right can be used to hide / show line numbers
|
2019-07-26 20:15:24 +03:00
|
|
|
* PageUp / 'b' and PageDown / 'f'
|
|
|
|
* Half page 'u'p / 'd'own
|
|
|
|
* Home and End for start / end of the document
|
|
|
|
* < to go to the start of the document
|
|
|
|
* > to go to the end of the document
|
|
|
|
* RETURN moves down one line
|
|
|
|
* SPACE moves down a page
|
|
|
|
|
|
|
|
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
|
|
|
|
-------------------------------------
|
|
|
|
Put the following line in your .bashrc or .bash_profile:
|
|
|
|
export PAGER=/usr/local/bin/moar.rb
|
|
|
|
|
|
|
|
Source Code
|
|
|
|
-----------
|
|
|
|
Available at https://github.com/walles/moar/.
|
2019-07-26 10:26:58 +03:00
|
|
|
`)
|
|
|
|
|
2019-06-10 22:50:31 +03:00
|
|
|
// NewPager creates a new Pager
|
2019-10-25 20:24:37 +03:00
|
|
|
func NewPager(r *Reader) *Pager {
|
|
|
|
return &Pager{
|
2019-06-12 22:55:09 +03:00
|
|
|
reader: r,
|
2019-06-15 10:23:53 +03:00
|
|
|
quit: false,
|
2019-06-12 22:55:09 +03:00
|
|
|
firstLineOneBased: 1,
|
2021-04-10 08:16:34 +03:00
|
|
|
ShowLineNumbers: true,
|
m: make Page a method
Page is a great function, but it is limited, because it makes too many choices
for the user. The global DeInit was a good start, but really that shouldve always
been a Pager field.
Move DeInit inside Pager, and set default to true as before in the NewPager
function. Then, users can call Page, but still have control over DeInit, as well
as Line Numbers, and not have to worry about creating a Screen. Example code:
r := m.NewReaderFromText("moar", strings.Repeat("moar\n", 99))
p := m.NewPager(r)
p.DeInit, p.ShowLineNumbers = false, false
p.Page()
2021-04-12 05:55:03 +03:00
|
|
|
DeInit: true,
|
2019-06-10 22:50:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) addSearchFooter() {
|
2019-06-28 23:41:29 +03:00
|
|
|
_, height := p.screen.Size()
|
|
|
|
|
|
|
|
pos := 0
|
2019-06-29 12:24:03 +03:00
|
|
|
for _, token := range "Search: " + p.searchString {
|
2021-04-15 16:16:06 +03:00
|
|
|
p.screen.SetCell(pos, height-1, twin.NewCell(token, twin.StyleDefault))
|
2019-06-28 23:41:29 +03:00
|
|
|
pos++
|
|
|
|
}
|
2019-06-29 12:20:48 +03:00
|
|
|
|
2019-06-29 12:24:03 +03:00
|
|
|
// Add a cursor
|
2021-04-15 16:16:06 +03:00
|
|
|
p.screen.SetCell(pos, height-1, twin.NewCell(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
|
2019-06-28 23:41:29 +03:00
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) setFooter(footer string) {
|
2021-05-22 13:01:59 +03:00
|
|
|
width, height := p.screen.Size()
|
2019-06-12 08:07:13 +03:00
|
|
|
|
2021-05-29 15:38:41 +03:00
|
|
|
pos := 0
|
2022-02-18 21:09:05 +03:00
|
|
|
var footerStyle twin.Style
|
|
|
|
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))
|
|
|
|
}
|
2021-05-29 15:38:41 +03:00
|
|
|
for _, token := range footer {
|
|
|
|
p.screen.SetCell(pos, height-1, twin.NewCell(token, footerStyle))
|
|
|
|
pos++
|
2021-01-12 23:53:09 +03:00
|
|
|
}
|
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
|
|
|
|
2021-05-29 15:38:41 +03:00
|
|
|
for ; pos < width; pos++ {
|
|
|
|
p.screen.SetCell(pos, height-1, twin.NewCell(' ', footerStyle))
|
Make line number configurable through code
diff --git m/pager.go m/pager.go
index 2133057..ec81976 100644
--- m/pager.go
+++ m/pager.go
@@ -40,6 +40,8 @@ type Pager struct {
isShowingHelp bool
preHelpState *_PreHelpState
+
+ lineNumbersWanted bool
}
type _PreHelpState struct {
@@ -60,6 +62,7 @@ Quitting
Moving around
-------------
* Arrow keys
+* Left / right can be used to hide / show line numbers
* PageUp / 'b' and PageDown / 'f'
* Half page 'u'p / 'd'own
* Home and End for start / end of the document
@@ -98,6 +101,7 @@ func NewPager(r *Reader) *Pager {
reader: r,
quit: false,
firstLineOneBased: 1,
+ lineNumbersWanted: true,
}
}
@@ -106,7 +110,7 @@ func (p *Pager) _AddLine(logger *log.Logger, fileLineNumber *int, maxPrefixLengt
prefixLength := 0
lineNumberString := ""
- if fileLineNumber != nil {
+ if maxPrefixLength > 0 && fileLineNumber != nil {
prefixLength = maxPrefixLength
lineNumberString = fmt.Sprintf("%*d ", prefixLength-1, *fileLineNumber)
}
@@ -207,6 +211,10 @@ 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 {
+ maxPrefixLength = 0
+ }
+
screenLineNumber := 0
for i, line := range lines.lines {
lineNumber := p.firstLineOneBased + i
Change-Id: I79cd0d46e8590cfdcd9e0ee942eeb0098676d25d
2019-11-19 13:28:28 +03:00
|
|
|
}
|
2021-05-29 15:38:41 +03:00
|
|
|
}
|
Make line number configurable through code
diff --git m/pager.go m/pager.go
index 2133057..ec81976 100644
--- m/pager.go
+++ m/pager.go
@@ -40,6 +40,8 @@ type Pager struct {
isShowingHelp bool
preHelpState *_PreHelpState
+
+ lineNumbersWanted bool
}
type _PreHelpState struct {
@@ -60,6 +62,7 @@ Quitting
Moving around
-------------
* Arrow keys
+* Left / right can be used to hide / show line numbers
* PageUp / 'b' and PageDown / 'f'
* Half page 'u'p / 'd'own
* Home and End for start / end of the document
@@ -98,6 +101,7 @@ func NewPager(r *Reader) *Pager {
reader: r,
quit: false,
firstLineOneBased: 1,
+ lineNumbersWanted: true,
}
}
@@ -106,7 +110,7 @@ func (p *Pager) _AddLine(logger *log.Logger, fileLineNumber *int, maxPrefixLengt
prefixLength := 0
lineNumberString := ""
- if fileLineNumber != nil {
+ if maxPrefixLength > 0 && fileLineNumber != nil {
prefixLength = maxPrefixLength
lineNumberString = fmt.Sprintf("%*d ", prefixLength-1, *fileLineNumber)
}
@@ -207,6 +211,10 @@ 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 {
+ maxPrefixLength = 0
+ }
+
screenLineNumber := 0
for i, line := range lines.lines {
lineNumber := p.firstLineOneBased + i
Change-Id: I79cd0d46e8590cfdcd9e0ee942eeb0098676d25d
2019-11-19 13:28:28 +03:00
|
|
|
|
2019-10-25 20:24:37 +03:00
|
|
|
// Quit leaves the help screen or quits the pager
|
|
|
|
func (p *Pager) Quit() {
|
2019-07-26 10:26:58 +03:00
|
|
|
if !p.isShowingHelp {
|
|
|
|
p.quit = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset help
|
|
|
|
p.isShowingHelp = false
|
|
|
|
p.reader = p.preHelpState.reader
|
|
|
|
p.firstLineOneBased = p.preHelpState.firstLineOneBased
|
|
|
|
p.leftColumnZeroBased = p.preHelpState.leftColumnZeroBased
|
|
|
|
p.preHelpState = nil
|
2019-06-11 22:21:12 +03:00
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) scrollToSearchHits() {
|
2019-06-30 23:16:04 +03:00
|
|
|
if p.searchPattern == nil {
|
|
|
|
// This is not a search
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
firstHitLine := p.findFirstHitLineOneBased(p.firstLineOneBased, false)
|
2019-07-06 08:45:07 +03:00
|
|
|
if firstHitLine == nil {
|
|
|
|
// No match, give up
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
if *firstHitLine <= p.getLastVisibleLineOneBased() {
|
2019-07-06 08:45:07 +03:00
|
|
|
// Already on-screen, never mind
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p.firstLineOneBased = *firstHitLine
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) getLastVisibleLineOneBased() int {
|
2019-06-30 23:16:04 +03:00
|
|
|
firstVisibleLineOneBased := p.firstLineOneBased
|
|
|
|
_, windowHeight := p.screen.Size()
|
|
|
|
|
|
|
|
// If first line is 1 and window is 2 high, and one line is the status
|
|
|
|
// line, the last line will be 1 + 2 - 2 = 1
|
2019-07-06 08:45:07 +03:00
|
|
|
return firstVisibleLineOneBased + windowHeight - 2
|
|
|
|
}
|
2019-06-30 16:06:33 +03:00
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) findFirstHitLineOneBased(firstLineOneBased int, backwards bool) *int {
|
2019-07-06 08:45:07 +03:00
|
|
|
lineNumber := firstLineOneBased
|
2019-06-30 23:16:04 +03:00
|
|
|
for {
|
|
|
|
line := p.reader.GetLine(lineNumber)
|
|
|
|
if line == nil {
|
|
|
|
// No match, give up
|
2019-07-06 08:45:07 +03:00
|
|
|
return nil
|
2019-06-30 23:16:04 +03:00
|
|
|
}
|
|
|
|
|
Parse lines on demand and only once
This improves line processing performance by 40%.
Fixes #36.
diff --git m/ansiTokenizer.go m/ansiTokenizer.go
index d991e23..056a227 100644
--- m/ansiTokenizer.go
+++ m/ansiTokenizer.go
@@ -23,6 +23,44 @@ type Token struct {
Style tcell.Style
}
+// A Line represents a line of text that can / will be paged
+type Line struct {
+ raw *string
+ plain *string
+ tokens []Token
+}
+
+// NewLine creates a new Line from a (potentially ANSI / man page formatted) string
+func NewLine(raw string) *Line {
+ return &Line{
+ raw: &raw,
+ plain: nil,
+ tokens: nil,
+ }
+}
+
+// Tokens returns a representation of the string split into styled tokens
+func (line *Line) Tokens() []Token {
+ line.parse()
+ return line.tokens
+}
+
+// Plain returns a plain text representation of the initial string
+func (line *Line) Plain() string {
+ line.parse()
+ return *line.plain
+}
+
+func (line *Line) parse() {
+ if line.raw == nil {
+ // Already done
+ return
+ }
+
+ line.tokens, line.plain = tokensFromString(*line.raw)
+ line.raw = nil
+}
+
// SetManPageFormatFromEnv parses LESS_TERMCAP_xx environment variables and
// adapts the moar output accordingly.
func SetManPageFormatFromEnv() {
diff --git m/pager.go m/pager.go
index 412e05b..98efa9a 100644
--- m/pager.go
+++ m/pager.go
@@ -111,7 +111,7 @@ func NewPager(r *Reader) *Pager {
}
}
-func (p *Pager) _AddLine(fileLineNumber *int, maxPrefixLength int, screenLineNumber int, line string) {
+func (p *Pager) _AddLine(fileLineNumber *int, maxPrefixLength int, screenLineNumber int, line *Line) {
screenWidth, _ := p.screen.Size()
prefixLength := 0
@@ -138,7 +138,7 @@ func (p *Pager) _AddLine(fileLineNumber *int, maxPrefixLength int, screenLineNum
func createScreenLine(
stringIndexAtColumnZero int,
screenColumnsCount int,
- line string,
+ line *Line,
search *regexp.Regexp,
) []Token {
var returnMe []Token
@@ -152,14 +152,14 @@ func createScreenLine(
searchHitDelta = -1
}
- tokens, plainString := tokensFromString(line)
- if stringIndexAtColumnZero >= len(tokens) {
+ if stringIndexAtColumnZero >= len(line.Tokens()) {
// Nothing (more) to display, never mind
return returnMe
}
- matchRanges := getMatchRanges(plainString, search)
- for _, token := range tokens[stringIndexAtColumnZero:] {
+ plain := line.Plain()
+ matchRanges := getMatchRanges(&plain, search)
+ for _, token := range line.Tokens()[stringIndexAtColumnZero:] {
if len(returnMe) >= screenColumnsCount {
// We are trying to add a character to the right of the screen.
// Indicate that this line continues to the right.
@@ -232,7 +232,8 @@ func (p *Pager) _AddLines(spinner string) {
// This happens when we're done
eofSpinner = "---"
}
- p._AddLine(nil, 0, screenLineNumber, _EofMarkerFormat+eofSpinner)
+ spinnerLine := NewLine(_EofMarkerFormat + eofSpinner)
+ p._AddLine(nil, 0, screenLineNumber, spinnerLine)
switch p.mode {
case _Searching:
@@ -329,8 +330,8 @@ func (p *Pager) _FindFirstHitLineOneBased(firstLineOneBased int, backwards bool)
return nil
}
- _, lineText := tokensFromString(*line)
- if p.searchPattern.MatchString(*lineText) {
+ lineText := line.Plain()
+ if p.searchPattern.MatchString(lineText) {
return &lineNumber
}
diff --git m/pager_test.go m/pager_test.go
index 65fa3c2..ce0f79b 100644
--- m/pager_test.go
+++ m/pager_test.go
@@ -265,13 +265,15 @@ func assertTokenRangesEqual(t *testing.T, actual []Token, expected []Token) {
}
func TestCreateScreenLineBase(t *testing.T) {
- line := createScreenLine(0, 3, "", nil)
- assert.Assert(t, len(line) == 0)
+ line := NewLine("")
+ screenLine := createScreenLine(0, 3, line, nil)
+ assert.Assert(t, len(screenLine) == 0)
}
func TestCreateScreenLineOverflowRight(t *testing.T) {
- line := createScreenLine(0, 3, "012345", nil)
- assertTokenRangesEqual(t, line, []Token{
+ line := NewLine("012345")
+ screenLine := createScreenLine(0, 3, line, nil)
+ assertTokenRangesEqual(t, screenLine, []Token{
createExpectedCell('0', tcell.StyleDefault),
createExpectedCell('1', tcell.StyleDefault),
createExpectedCell('>', tcell.StyleDefault.Reverse(true)),
@@ -279,8 +281,9 @@ func TestCreateScreenLineOverflowRight(t *testing.T) {
}
func TestCreateScreenLineUnderflowLeft(t *testing.T) {
- line := createScreenLine(1, 3, "012", nil)
- assertTokenRangesEqual(t, line, []Token{
+ line := NewLine("012")
+ screenLine := createScreenLine(1, 3, line, nil)
+ assertTokenRangesEqual(t, screenLine, []Token{
createExpectedCell('<', tcell.StyleDefault.Reverse(true)),
createExpectedCell('1', tcell.StyleDefault),
createExpectedCell('2', tcell.StyleDefault),
@@ -293,8 +296,9 @@ func TestCreateScreenLineSearchHit(t *testing.T) {
panic(err)
}
- line := createScreenLine(0, 3, "abc", pattern)
- assertTokenRangesEqual(t, line, []Token{
+ line := NewLine("abc")
+ screenLine := createScreenLine(0, 3, line, pattern)
+ assertTokenRangesEqual(t, screenLine, []Token{
createExpectedCell('a', tcell.StyleDefault),
createExpectedCell('b', tcell.StyleDefault.Reverse(true)),
createExpectedCell('c', tcell.StyleDefault),
@@ -307,8 +311,9 @@ func TestCreateScreenLineUtf8SearchHit(t *testing.T) {
panic(err)
}
- line := createScreenLine(0, 3, "åäö", pattern)
- assertTokenRangesEqual(t, line, []Token{
+ line := NewLine("åäö")
+ screenLine := createScreenLine(0, 3, line, pattern)
+ assertTokenRangesEqual(t, screenLine, []Token{
createExpectedCell('å', tcell.StyleDefault),
createExpectedCell('ä', tcell.StyleDefault.Reverse(true)),
createExpectedCell('ö', tcell.StyleDefault),
@@ -318,9 +323,10 @@ func TestCreateScreenLineUtf8SearchHit(t *testing.T) {
func TestCreateScreenLineScrolledUtf8SearchHit(t *testing.T) {
pattern := regexp.MustCompile("ä")
- line := createScreenLine(1, 4, "ååäö", pattern)
+ line := NewLine("ååäö")
+ screenLine := createScreenLine(1, 4, line, pattern)
- assertTokenRangesEqual(t, line, []Token{
+ assertTokenRangesEqual(t, screenLine, []Token{
createExpectedCell('<', tcell.StyleDefault.Reverse(true)),
createExpectedCell('å', tcell.StyleDefault),
createExpectedCell('ä', tcell.StyleDefault.Reverse(true)),
@@ -331,9 +337,10 @@ func TestCreateScreenLineScrolledUtf8SearchHit(t *testing.T) {
func TestCreateScreenLineScrolled2Utf8SearchHit(t *testing.T) {
pattern := regexp.MustCompile("ä")
- line := createScreenLine(2, 4, "åååäö", pattern)
+ line := NewLine("åååäö")
+ screenLine := createScreenLine(2, 4, line, pattern)
- assertTokenRangesEqual(t, line, []Token{
+ assertTokenRangesEqual(t, screenLine, []Token{
createExpectedCell('<', tcell.StyleDefault.Reverse(true)),
createExpectedCell('å', tcell.StyleDefault),
createExpectedCell('ä', tcell.StyleDefault.Reverse(true)),
diff --git m/reader.go m/reader.go
index 418c4c5..d47b710 100644
--- m/reader.go
+++ m/reader.go
@@ -29,7 +29,7 @@ import (
//
// This package provides query methods for the struct, no peeking!!
type Reader struct {
- lines []string
+ lines []*Line
name *string
lock *sync.Mutex
err error
@@ -41,7 +41,7 @@ type Reader struct {
// Lines contains a number of lines from the reader, plus metadata
type Lines struct {
- lines []string
+ lines []*Line
// One-based line number of the first line returned
firstLineOneBased int
@@ -136,7 +136,7 @@ func readStream(stream io.Reader, reader *Reader, fromFilter *exec.Cmd) {
}
reader.lock.Lock()
- reader.lines = append(reader.lines, string(completeLine))
+ reader.lines = append(reader.lines, NewLine(string(completeLine)))
reader.lock.Unlock()
// This is how to do a non-blocking write to a channel:
@@ -172,7 +172,7 @@ func NewReaderFromStream(name string, reader io.Reader) *Reader {
// If fromFilter is not nil this method will wait() for it,
// and effectively takes over ownership for it.
func newReaderFromStream(reader io.Reader, fromFilter *exec.Cmd) *Reader {
- var lines []string
+ var lines []*Line
var lock = &sync.Mutex{}
done := make(chan bool, 1)
@@ -201,9 +201,11 @@ func newReaderFromStream(reader io.Reader, fromFilter *exec.Cmd) *Reader {
// Moar in the bottom left corner of the screen.
func NewReaderFromText(name string, text string) *Reader {
noExternalNewlines := strings.Trim(text, "\n")
- lines := []string{}
+ lines := []*Line{}
if len(noExternalNewlines) > 0 {
- lines = strings.Split(noExternalNewlines, "\n")
+ for _, line := range strings.Split(noExternalNewlines, "\n") {
+ lines = append(lines, NewLine(line))
+ }
}
done := make(chan bool, 1)
done <- true
@@ -380,7 +382,7 @@ func (r *Reader) GetLineCount() int {
}
// GetLine gets a line. If the requested line number is out of bounds, nil is returned.
-func (r *Reader) GetLine(lineNumberOneBased int) *string {
+func (r *Reader) GetLine(lineNumberOneBased int) *Line {
r.lock.Lock()
defer r.lock.Unlock()
@@ -390,7 +392,7 @@ func (r *Reader) GetLine(lineNumberOneBased int) *string {
if lineNumberOneBased > len(r.lines) {
return nil
}
- return &r.lines[lineNumberOneBased-1]
+ return r.lines[lineNumberOneBased-1]
}
// GetLines gets the indicated lines from the input
diff --git m/reader_test.go m/reader_test.go
index 2ba7326..0e2aed2 100644
--- m/reader_test.go
+++ m/reader_test.go
@@ -158,8 +158,8 @@ func TestGetLongLine(t *testing.T) {
assert.Equal(t, len(lines.lines), 1)
line := lines.lines[0]
- assert.Assert(t, strings.HasPrefix(line, "1 2 3 4"), "<%s>", line)
- assert.Assert(t, strings.HasSuffix(line, "0123456789"), line)
+ assert.Assert(t, strings.HasPrefix(line.Plain(), "1 2 3 4"), "<%s>", line)
+ assert.Assert(t, strings.HasSuffix(line.Plain(), "0123456789"), line)
stat, err := os.Stat(file)
if err != nil {
@@ -168,7 +168,7 @@ func TestGetLongLine(t *testing.T) {
fileSize := stat.Size()
// The "+1" is because the Reader strips off the ending linefeed
- assert.Equal(t, len(line)+1, int(fileSize))
+ assert.Equal(t, len(line.Plain())+1, int(fileSize))
}
func getReaderWithLineCount(totalLines int) *Reader {
@@ -219,7 +219,7 @@ func testCompressedFile(t *testing.T, filename string) {
panic(err)
}
- assert.Equal(t, reader.GetLines(1, 5).lines[0], "This is a compressed file", "%s", filename)
+ assert.Equal(t, reader.GetLines(1, 5).lines[0].Plain(), "This is a compressed file", "%s", filename)
}
func TestCompressedFiles(t *testing.T) {
Change-Id: Id8671001ec7c1038e2df0b87a45d346a1f1dd663
2021-01-11 12:42:34 +03:00
|
|
|
lineText := line.Plain()
|
|
|
|
if p.searchPattern.MatchString(lineText) {
|
2019-07-06 08:45:07 +03:00
|
|
|
return &lineNumber
|
2019-06-30 23:16:04 +03:00
|
|
|
}
|
2019-06-30 16:06:33 +03:00
|
|
|
|
2019-07-06 11:31:40 +03:00
|
|
|
if backwards {
|
|
|
|
lineNumber--
|
|
|
|
} else {
|
|
|
|
lineNumber++
|
|
|
|
}
|
2019-06-30 23:16:04 +03:00
|
|
|
}
|
2019-06-30 16:06:33 +03:00
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) scrollToNextSearchHit() {
|
2019-07-06 08:45:07 +03:00
|
|
|
if p.searchPattern == nil {
|
|
|
|
// Nothing to search for, never mind
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-06 11:31:40 +03:00
|
|
|
if p.reader.GetLineCount() == 0 {
|
|
|
|
// Nothing to search in, never mind
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-06 08:45:07 +03:00
|
|
|
var firstSearchLineOneBased int
|
|
|
|
|
|
|
|
switch p.mode {
|
|
|
|
case _Viewing:
|
|
|
|
// Start searching on the first line below the bottom of the screen
|
2022-02-19 10:49:10 +03:00
|
|
|
firstSearchLineOneBased = p.getLastVisibleLineOneBased() + 1
|
2019-07-06 08:45:07 +03:00
|
|
|
|
|
|
|
case _NotFound:
|
|
|
|
// Restart searching from the top
|
|
|
|
p.mode = _Viewing
|
|
|
|
firstSearchLineOneBased = 1
|
|
|
|
|
|
|
|
default:
|
2019-07-06 08:54:33 +03:00
|
|
|
panic(fmt.Sprint("Unknown search mode when finding next: ", p.mode))
|
2019-07-06 08:45:07 +03:00
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
firstHitLine := p.findFirstHitLineOneBased(firstSearchLineOneBased, false)
|
2019-07-06 11:31:40 +03:00
|
|
|
if firstHitLine == nil {
|
|
|
|
p.mode = _NotFound
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.firstLineOneBased = *firstHitLine
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) scrollToPreviousSearchHit() {
|
2019-07-06 11:31:40 +03:00
|
|
|
if p.searchPattern == nil {
|
|
|
|
// Nothing to search for, never mind
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.reader.GetLineCount() == 0 {
|
|
|
|
// Nothing to search in, never mind
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var firstSearchLineOneBased int
|
|
|
|
|
|
|
|
switch p.mode {
|
|
|
|
case _Viewing:
|
|
|
|
// Start searching on the first line above the top of the screen
|
|
|
|
firstSearchLineOneBased = p.firstLineOneBased - 1
|
|
|
|
|
|
|
|
case _NotFound:
|
|
|
|
// Restart searching from the bottom
|
|
|
|
p.mode = _Viewing
|
|
|
|
firstSearchLineOneBased = p.reader.GetLineCount()
|
|
|
|
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprint("Unknown search mode when finding previous: ", p.mode))
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
firstHitLine := p.findFirstHitLineOneBased(firstSearchLineOneBased, true)
|
2019-07-06 08:45:07 +03:00
|
|
|
if firstHitLine == nil {
|
|
|
|
p.mode = _NotFound
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.firstLineOneBased = *firstHitLine
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) updateSearchPattern() {
|
2020-12-30 00:57:44 +03:00
|
|
|
p.searchPattern = toPattern(p.searchString)
|
2019-07-06 14:51:48 +03:00
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
p.scrollToSearchHits()
|
2019-07-06 14:51:48 +03:00
|
|
|
|
|
|
|
// FIXME: If the user is typing, indicate to user if we didn't find anything
|
|
|
|
}
|
2019-06-29 19:29:37 +03:00
|
|
|
|
2020-12-30 00:57:44 +03:00
|
|
|
// toPattern compiles a search string into a pattern.
|
2019-07-06 14:51:48 +03:00
|
|
|
//
|
|
|
|
// If the string contains only lower-case letter the pattern will be case insensitive.
|
|
|
|
//
|
|
|
|
// If the string is empty the pattern will be nil.
|
|
|
|
//
|
|
|
|
// If the string does not compile into a regexp the pattern will match the string verbatim
|
2020-12-30 00:57:44 +03:00
|
|
|
func toPattern(compileMe string) *regexp.Regexp {
|
2019-07-06 14:51:48 +03:00
|
|
|
if len(compileMe) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2019-06-30 16:06:33 +03:00
|
|
|
|
2019-07-06 23:16:08 +03:00
|
|
|
hasUppercase := false
|
|
|
|
for _, char := range compileMe {
|
|
|
|
if unicode.IsUpper(char) {
|
|
|
|
hasUppercase = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Smart case; be case insensitive unless there are upper case chars
|
|
|
|
// in the search string
|
|
|
|
prefix := "(?i)"
|
|
|
|
if hasUppercase {
|
|
|
|
prefix = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
pattern, err := regexp.Compile(prefix + compileMe)
|
2019-06-29 19:29:37 +03:00
|
|
|
if err == nil {
|
|
|
|
// Search string is a regexp
|
2019-07-06 14:51:48 +03:00
|
|
|
return pattern
|
2019-06-29 19:29:37 +03:00
|
|
|
}
|
|
|
|
|
2019-07-06 23:16:08 +03:00
|
|
|
pattern, err = regexp.Compile(prefix + regexp.QuoteMeta(compileMe))
|
2019-06-29 19:29:37 +03:00
|
|
|
if err == nil {
|
|
|
|
// Pattern matching the string exactly
|
2019-07-06 14:51:48 +03:00
|
|
|
return pattern
|
2019-06-29 19:29:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unable to create a match-string-verbatim pattern
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2019-11-02 14:17:38 +03:00
|
|
|
// From: https://stackoverflow.com/a/57005674/473672
|
|
|
|
func removeLastChar(s string) string {
|
|
|
|
r, size := utf8.DecodeLastRuneInString(s)
|
|
|
|
if r == utf8.RuneError && (size == 0 || size == 1) {
|
|
|
|
size = 0
|
|
|
|
}
|
|
|
|
return s[:len(s)-size]
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) scrollToEnd() {
|
2021-05-27 07:48:51 +03:00
|
|
|
p.firstLineOneBased = p.reader.GetLineCount()
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) onSearchKey(key twin.KeyCode) {
|
2019-06-28 23:41:29 +03:00
|
|
|
switch key {
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyEscape, twin.KeyEnter:
|
2019-07-06 08:45:07 +03:00
|
|
|
p.mode = _Viewing
|
2019-06-28 23:41:29 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyBackspace, twin.KeyDelete:
|
2019-06-29 12:20:48 +03:00
|
|
|
if len(p.searchString) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-02 14:17:38 +03:00
|
|
|
p.searchString = removeLastChar(p.searchString)
|
2022-02-19 10:49:10 +03:00
|
|
|
p.updateSearchPattern()
|
2019-06-29 12:20:48 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyUp:
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2019-08-04 08:40:26 +03:00
|
|
|
p.firstLineOneBased--
|
|
|
|
p.mode = _Viewing
|
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyDown:
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2019-08-04 08:40:26 +03:00
|
|
|
p.firstLineOneBased++
|
|
|
|
p.mode = _Viewing
|
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyPgUp:
|
2019-08-04 08:40:26 +03:00
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased -= (height - 1)
|
|
|
|
p.mode = _Viewing
|
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyPgDown:
|
2019-08-04 08:40:26 +03:00
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased += (height - 1)
|
|
|
|
p.mode = _Viewing
|
|
|
|
|
2019-06-28 23:41:29 +03:00
|
|
|
default:
|
2019-12-06 21:36:31 +03:00
|
|
|
log.Debugf("Unhandled search key event %v", key)
|
2019-06-28 23:41:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) moveRight(delta int) {
|
2021-04-10 08:16:34 +03:00
|
|
|
if p.ShowLineNumbers && delta > 0 {
|
|
|
|
p.ShowLineNumbers = false
|
2019-11-19 13:41:56 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.leftColumnZeroBased == 0 && delta < 0 {
|
2021-04-10 08:16:34 +03:00
|
|
|
p.ShowLineNumbers = true
|
2019-11-19 13:41:56 +03:00
|
|
|
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-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) onKey(keyCode twin.KeyCode) {
|
2019-07-06 08:45:07 +03:00
|
|
|
if p.mode == _Searching {
|
2022-02-19 10:49:10 +03:00
|
|
|
p.onSearchKey(keyCode)
|
2019-06-28 23:41:29 +03:00
|
|
|
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
|
2019-06-28 23:41:29 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
switch keyCode {
|
|
|
|
case twin.KeyEscape:
|
2019-06-15 09:10:56 +03:00
|
|
|
p.Quit()
|
2019-06-12 22:55:09 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyUp:
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2019-06-12 22:55:09 +03:00
|
|
|
p.firstLineOneBased--
|
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyDown, twin.KeyEnter:
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2019-06-12 22:55:09 +03:00
|
|
|
p.firstLineOneBased++
|
2019-06-13 07:14:41 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyRight:
|
2022-02-19 10:49:10 +03:00
|
|
|
p.moveRight(16)
|
2019-07-06 14:33:41 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyLeft:
|
2022-02-19 10:49:10 +03:00
|
|
|
p.moveRight(-16)
|
2019-07-06 08:45:07 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyHome:
|
2019-06-13 07:14:41 +03:00
|
|
|
p.firstLineOneBased = 1
|
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyEnd:
|
Fix a crash on 32 bit systems
Fixes #18
diff --git m/pager.go m/pager.go
index 178c828..b7cce09 100644
--- m/pager.go
+++ m/pager.go
@@ -3,7 +3,6 @@ package m
import (
"fmt"
"log"
- "math"
"os"
"regexp"
"time"
@@ -507,7 +506,7 @@ func (p *Pager) _OnKey(logger *log.Logger, key tcell.Key) {
p.firstLineOneBased = 1
case tcell.KeyEnd:
- p.firstLineOneBased = math.MaxInt32
+ p.firstLineOneBased = p.reader.GetLineCount()
case tcell.KeyPgDn:
_, height := p.screen.Size()
@@ -565,7 +564,7 @@ func (p *Pager) _OnRune(logger *log.Logger, char rune) {
p.firstLineOneBased = 1
case '>', 'G':
- p.firstLineOneBased = math.MaxInt32
+ p.firstLineOneBased = p.reader.GetLineCount()
case 'f', ' ':
_, height := p.screen.Size()
Change-Id: I17712d93322703ae3fa5b54a5a07169ddd1357c4
2019-11-13 18:17:26 +03:00
|
|
|
p.firstLineOneBased = p.reader.GetLineCount()
|
2019-06-13 07:14:41 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyPgDown:
|
2019-06-13 07:21:43 +03:00
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased += (height - 1)
|
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.KeyPgUp:
|
2019-06-13 07:21:43 +03:00
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased -= (height - 1)
|
2019-06-16 11:02:19 +03:00
|
|
|
|
|
|
|
default:
|
2021-04-15 16:16:06 +03:00
|
|
|
log.Debugf("Unhandled key event %v", keyCode)
|
2019-06-11 22:28:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) onSearchRune(char rune) {
|
2019-06-29 12:20:48 +03:00
|
|
|
p.searchString = p.searchString + string(char)
|
2022-02-19 10:49:10 +03:00
|
|
|
p.updateSearchPattern()
|
2019-06-28 23:41:29 +03:00
|
|
|
}
|
|
|
|
|
2022-02-19 10:49:10 +03:00
|
|
|
func (p *Pager) onRune(char rune) {
|
2019-07-06 08:45:07 +03:00
|
|
|
if p.mode == _Searching {
|
2022-02-19 10:49:10 +03:00
|
|
|
p.onSearchRune(char)
|
2019-06-28 23:41:29 +03:00
|
|
|
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-28 23:41:29 +03:00
|
|
|
|
2019-06-11 22:32:24 +03:00
|
|
|
switch char {
|
|
|
|
case 'q':
|
2019-06-15 09:10:56 +03:00
|
|
|
p.Quit()
|
2019-06-13 07:21:43 +03:00
|
|
|
|
2020-09-05 21:31:21 +03:00
|
|
|
case '?':
|
2019-07-26 10:26:58 +03:00
|
|
|
if !p.isShowingHelp {
|
|
|
|
p.preHelpState = &_PreHelpState{
|
|
|
|
reader: p.reader,
|
|
|
|
firstLineOneBased: p.firstLineOneBased,
|
|
|
|
leftColumnZeroBased: p.leftColumnZeroBased,
|
|
|
|
}
|
|
|
|
p.reader = _HelpReader
|
|
|
|
p.firstLineOneBased = 1
|
|
|
|
p.leftColumnZeroBased = 0
|
|
|
|
p.isShowingHelp = true
|
|
|
|
}
|
|
|
|
|
2019-06-13 16:56:06 +03:00
|
|
|
case 'k', 'y':
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2019-06-13 16:56:06 +03:00
|
|
|
p.firstLineOneBased--
|
|
|
|
|
|
|
|
case 'j', 'e':
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2019-06-13 16:56:06 +03:00
|
|
|
p.firstLineOneBased++
|
|
|
|
|
2020-09-05 21:31:21 +03:00
|
|
|
case 'l':
|
|
|
|
// vim right
|
2022-02-19 10:49:10 +03:00
|
|
|
p.moveRight(16)
|
2020-09-05 21:31:21 +03:00
|
|
|
|
|
|
|
case 'h':
|
|
|
|
// vim left
|
2022-02-19 10:49:10 +03:00
|
|
|
p.moveRight(-16)
|
2020-09-05 21:31:21 +03:00
|
|
|
|
2019-06-13 07:14:41 +03:00
|
|
|
case '<', 'g':
|
|
|
|
p.firstLineOneBased = 1
|
2019-06-13 07:21:43 +03:00
|
|
|
|
2019-06-13 07:14:41 +03:00
|
|
|
case '>', 'G':
|
2022-02-19 10:49:10 +03:00
|
|
|
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()
|
|
|
|
p.firstLineOneBased += (height - 1)
|
|
|
|
|
|
|
|
case 'b':
|
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased -= (height - 1)
|
|
|
|
|
2019-07-26 20:15:24 +03:00
|
|
|
case 'u':
|
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased -= (height / 2)
|
|
|
|
|
|
|
|
case 'd':
|
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased += (height / 2)
|
|
|
|
|
2019-06-28 23:41:29 +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
|
2019-06-28 23:41:29 +03:00
|
|
|
|
2019-07-06 08:45:07 +03:00
|
|
|
case 'n':
|
2022-02-19 10:49:10 +03:00
|
|
|
p.scrollToNextSearchHit()
|
2019-07-06 08:45:07 +03:00
|
|
|
|
2019-07-06 11:31:40 +03:00
|
|
|
case 'p', 'N':
|
2022-02-19 10:49:10 +03:00
|
|
|
p.scrollToPreviousSearchHit()
|
2019-07-06 08:45:07 +03:00
|
|
|
|
2021-05-22 16:32:49 +03:00
|
|
|
case 'w':
|
|
|
|
p.WrapLongLines = !p.WrapLongLines
|
|
|
|
|
2019-06-16 11:02:19 +03:00
|
|
|
default:
|
2019-12-06 21:36:31 +03:00
|
|
|
log.Debugf("Unhandled rune keypress '%s'", string(char))
|
2019-06-11 22:32:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-10 22:50:31 +03:00
|
|
|
// StartPaging brings up the pager on screen
|
2021-04-15 16:16:06 +03:00
|
|
|
func (p *Pager) StartPaging(screen twin.Screen) {
|
2022-02-27 11:39:16 +03:00
|
|
|
unprintableStyle = p.UnprintableStyle
|
2019-11-27 20:43:46 +03:00
|
|
|
SetManPageFormatFromEnv()
|
2019-10-30 22:29:29 +03:00
|
|
|
|
2019-06-15 09:10:56 +03:00
|
|
|
p.screen = screen
|
2019-06-11 19:29:30 +03:00
|
|
|
|
2019-07-11 19:52:20 +03:00
|
|
|
go func() {
|
|
|
|
for {
|
2021-04-15 16:16:06 +03:00
|
|
|
// Wait for new lines to appear...
|
2019-07-11 19:52:20 +03:00
|
|
|
<-p.reader.moreLinesAdded
|
2021-04-15 16:16:06 +03:00
|
|
|
|
|
|
|
// ... and notify the main loop so it can show them:
|
|
|
|
screen.Events() <- eventMoreLinesAvailable{}
|
2019-07-11 19:52:20 +03:00
|
|
|
|
2019-07-15 00:10:54 +03:00
|
|
|
// Delay updates a bit so that we don't waste time refreshing
|
|
|
|
// the screen too often.
|
2019-07-15 08:08:04 +03:00
|
|
|
//
|
|
|
|
// 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.
|
2019-07-15 00:10:54 +03:00
|
|
|
time.Sleep(200 * time.Millisecond)
|
2019-07-11 19:52:20 +03:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-10-16 07:01:49 +03:00
|
|
|
go func() {
|
2021-04-15 16:16:06 +03:00
|
|
|
// Spin the spinner as long as contents is still loading
|
2019-10-16 07:01:49 +03:00
|
|
|
done := false
|
2019-10-30 00:05:42 +03:00
|
|
|
spinnerFrames := [...]string{"/.\\", "-o-", "\\O/", "| |"}
|
2019-10-16 07:01:49 +03:00
|
|
|
spinnerIndex := 0
|
|
|
|
for {
|
|
|
|
// Break this loop on the reader.done signal...
|
|
|
|
select {
|
|
|
|
case <-p.reader.done:
|
|
|
|
done = true
|
|
|
|
default:
|
|
|
|
// This default case makes this an async read
|
|
|
|
}
|
|
|
|
|
|
|
|
if done {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
screen.Events() <- eventSpinnerUpdate{spinnerFrames[spinnerIndex]}
|
2019-10-16 07:01:49 +03:00
|
|
|
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{""}
|
2019-10-16 07:01:49 +03:00
|
|
|
}()
|
|
|
|
|
2019-06-11 19:29:30 +03:00
|
|
|
// Main loop
|
2019-10-16 07:01:49 +03:00
|
|
|
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, redraw the screen!
|
2022-02-19 10:49:10 +03:00
|
|
|
p.redraw(spinner)
|
2021-04-15 16:16:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
event := <-screen.Events()
|
|
|
|
switch event := event.(type) {
|
|
|
|
case twin.EventKeyCode:
|
2021-05-13 20:12:44 +03:00
|
|
|
log.Tracef("Handling key event %d...", event.KeyCode())
|
2022-02-19 10:49:10 +03:00
|
|
|
p.onKey(event.KeyCode())
|
2019-06-15 09:10:56 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.EventRune:
|
2021-05-13 20:12:44 +03:00
|
|
|
log.Tracef("Handling rune event '%c'/0x%04x...", event.Rune(), event.Rune())
|
2022-02-19 10:49:10 +03:00
|
|
|
p.onRune(event.Rune())
|
2021-04-15 16:16:06 +03:00
|
|
|
|
|
|
|
case twin.EventMouse:
|
2021-05-13 20:12:44 +03:00
|
|
|
log.Tracef("Handling mouse event %d...", event.Buttons())
|
2021-04-15 16:16:06 +03:00
|
|
|
switch event.Buttons() {
|
|
|
|
case twin.MouseWheelUp:
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2019-11-14 09:45:15 +03:00
|
|
|
p.firstLineOneBased--
|
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.MouseWheelDown:
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2019-11-14 09:45:15 +03:00
|
|
|
p.firstLineOneBased++
|
2019-11-14 09:46:54 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.MouseWheelLeft:
|
2022-02-19 10:49:10 +03:00
|
|
|
p.moveRight(-16)
|
2021-04-15 16:16:06 +03:00
|
|
|
|
|
|
|
case twin.MouseWheelRight:
|
2022-02-19 10:49:10 +03:00
|
|
|
p.moveRight(16)
|
2019-11-14 09:45:15 +03:00
|
|
|
}
|
|
|
|
|
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:
|
|
|
|
// Doing nothing here is fine; screen will be refreshed on the next
|
|
|
|
// iteration of the main loop.
|
|
|
|
|
|
|
|
case eventSpinnerUpdate:
|
|
|
|
spinner = event.spinner
|
2019-07-15 00:18:26 +03:00
|
|
|
|
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-11 19:29:30 +03:00
|
|
|
}
|
2019-06-15 10:23:53 +03:00
|
|
|
}
|
2019-07-11 19:34:10 +03:00
|
|
|
|
2019-11-19 17:18:40 +03:00
|
|
|
if p.reader.err != nil {
|
2019-12-06 21:36:31 +03:00
|
|
|
log.Warnf("Reader reported an error: %s", p.reader.err.Error())
|
2019-11-19 17:18:40 +03:00
|
|
|
}
|
2019-06-10 22:50:31 +03:00
|
|
|
}
|
2021-11-09 20:56:02 +03:00
|
|
|
|
|
|
|
// 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
|
|
|
|
_, height := p.screen.Size()
|
|
|
|
heightWithoutFooter := height - 1
|
|
|
|
lineCount := len(p.reader.GetLines(p.firstLineOneBased, heightWithoutFooter).lines)
|
|
|
|
|
|
|
|
if lineCount > 0 {
|
|
|
|
p.screen.ShowNLines(lineCount)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|