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-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
|
2022-02-20 09:48:08 +03:00
|
|
|
scrollPosition scrollPosition
|
2019-07-06 14:33:41 +03:00
|
|
|
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
|
2022-02-20 09:48:08 +03:00
|
|
|
scrollPosition scrollPosition
|
2019-07-26 10:26:58 +03:00
|
|
|
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
|
|
|
|
-------------------------------------
|
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/.
|
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 {
|
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{
|
2022-02-20 09:48:08 +03:00
|
|
|
reader: r,
|
|
|
|
quit: false,
|
|
|
|
ShowLineNumbers: true,
|
|
|
|
DeInit: true,
|
2022-02-27 10:15:46 +03:00
|
|
|
scrollPosition: newScrollPosition(name),
|
2019-06-10 22:50:31 +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
|
2022-02-20 09:48:08 +03:00
|
|
|
p.scrollPosition = p.preHelpState.scrollPosition
|
2019-07-26 10:26:58 +03:00
|
|
|
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) 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()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.PreviousLine(1)
|
2019-06-12 22:55:09 +03:00
|
|
|
|
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()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.NextLine(1)
|
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:
|
2022-02-27 10:15:46 +03:00
|
|
|
p.scrollPosition = newScrollPosition("Pager scroll position")
|
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.KeyPgDown:
|
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)
|
2019-06-13 07:21:43 +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)
|
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) 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,
|
2022-02-20 09:48:08 +03:00
|
|
|
scrollPosition: p.scrollPosition,
|
2019-07-26 10:26:58 +03:00
|
|
|
leftColumnZeroBased: p.leftColumnZeroBased,
|
|
|
|
}
|
|
|
|
p.reader = _HelpReader
|
2022-02-27 10:15:46 +03:00
|
|
|
p.scrollPosition = newScrollPosition("Pager scroll position")
|
2019-07-26 10:26:58 +03:00
|
|
|
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()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.PreviousLine(1)
|
2019-06-13 16:56:06 +03:00
|
|
|
|
|
|
|
case 'j', 'e':
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.NextLine(1)
|
2019-06-13 16:56:06 +03:00
|
|
|
|
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':
|
2022-02-27 10:15:46 +03:00
|
|
|
p.scrollPosition = newScrollPosition("Pager scroll position")
|
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()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.NextLine(height - 1)
|
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)
|
2019-06-13 07:21:43 +03:00
|
|
|
|
2019-07-26 20:15:24 +03:00
|
|
|
case 'u':
|
|
|
|
_, height := p.screen.Size()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.PreviousLine(height / 2)
|
2019-07-26 20:15:24 +03:00
|
|
|
|
|
|
|
case 'd':
|
|
|
|
_, height := p.screen.Size()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.NextLine(height / 2)
|
2019-07-26 20:15:24 +03:00
|
|
|
|
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()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.PreviousLine(1)
|
2019-11-14 09:45:15 +03:00
|
|
|
|
2021-04-15 16:16:06 +03:00
|
|
|
case twin.MouseWheelDown:
|
2021-05-29 15:38:41 +03:00
|
|
|
// Clipping is done in _Redraw()
|
2022-02-26 10:35:29 +03:00
|
|
|
p.scrollPosition = p.scrollPosition.NextLine(1)
|
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
|
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
|
|
|
|
}
|
2021-11-09 20:56:02 +03:00
|
|
|
|
2022-02-26 19:01:15 +03:00
|
|
|
if screenLinesCount > 0 {
|
|
|
|
p.screen.ShowNLines(screenLinesCount)
|
2021-11-09 20:56:02 +03:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|