2019-06-10 22:50:31 +03:00
|
|
|
package m
|
|
|
|
|
2019-06-11 19:29:30 +03:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2019-06-29 19:29:37 +03:00
|
|
|
"regexp"
|
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
|
|
|
"strconv"
|
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"
|
|
|
|
|
2020-10-30 10:19:13 +03:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2019-06-11 19:29:30 +03:00
|
|
|
)
|
|
|
|
|
2019-07-10 07:44:10 +03:00
|
|
|
// FIXME: Profile the pager while searching through a large file
|
|
|
|
|
2019-07-06 08:45:07 +03:00
|
|
|
type _PagerMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
_Viewing _PagerMode = 0
|
|
|
|
_Searching _PagerMode = 1
|
|
|
|
_NotFound _PagerMode = 2
|
|
|
|
)
|
|
|
|
|
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
|
|
|
|
var _NumberStyle = tcell.StyleDefault.Dim(true)
|
|
|
|
|
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
|
2019-07-06 14:33:41 +03:00
|
|
|
screen tcell.Screen
|
|
|
|
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
|
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!
|
|
|
|
|
|
|
|
Quitting
|
|
|
|
--------
|
|
|
|
* Press 'q' or ESC to quit
|
|
|
|
|
2019-11-28 20:14:09 +03:00
|
|
|
Miscellaneous
|
|
|
|
-------------
|
|
|
|
* Press CTRL-l to refresh screen if it has become garbled
|
|
|
|
|
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,
|
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
|
|
|
showLineNumbers: true,
|
2019-06-10 22:50:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-12 23:53:09 +03:00
|
|
|
func (p *Pager) _AddLine(fileLineNumber *int, numberPrefixLength int, screenLineNumber int, line *Line) {
|
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
|
|
|
screenWidth, _ := p.screen.Size()
|
|
|
|
|
|
|
|
lineNumberString := ""
|
2021-01-12 23:53:09 +03:00
|
|
|
if numberPrefixLength > 0 && fileLineNumber != nil {
|
|
|
|
lineNumberString = fmt.Sprintf("%*d ", numberPrefixLength-1, *fileLineNumber)
|
|
|
|
} else {
|
|
|
|
numberPrefixLength = 0
|
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
|
|
|
}
|
|
|
|
|
|
|
|
for column, digit := range lineNumberString {
|
2021-01-12 23:53:09 +03:00
|
|
|
if column >= numberPrefixLength {
|
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
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
p.screen.SetContent(column, screenLineNumber, digit, nil, _NumberStyle)
|
|
|
|
}
|
|
|
|
|
2021-01-12 23:53:09 +03:00
|
|
|
tokens := createScreenLine(p.leftColumnZeroBased, screenWidth-numberPrefixLength, line, p.searchPattern)
|
2019-11-05 23:09:45 +03:00
|
|
|
for column, token := range tokens {
|
2021-01-12 23:53:09 +03:00
|
|
|
p.screen.SetContent(column+numberPrefixLength, screenLineNumber, token.Rune, nil, token.Style)
|
2019-11-05 23:09:45 +03:00
|
|
|
}
|
|
|
|
}
|
2019-08-04 00:21:40 +03:00
|
|
|
|
2020-12-29 19:08:54 +03:00
|
|
|
func createScreenLine(
|
2019-11-05 23:09:45 +03:00
|
|
|
stringIndexAtColumnZero int,
|
|
|
|
screenColumnsCount int,
|
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
|
|
|
line *Line,
|
2019-11-05 23:09:45 +03:00
|
|
|
search *regexp.Regexp,
|
|
|
|
) []Token {
|
|
|
|
var returnMe []Token
|
2019-11-06 22:30:59 +03:00
|
|
|
searchHitDelta := 0
|
2019-11-05 23:09:45 +03:00
|
|
|
if stringIndexAtColumnZero > 0 {
|
|
|
|
// Indicate that it's possible to scroll left
|
|
|
|
returnMe = append(returnMe, Token{
|
|
|
|
Rune: '<',
|
|
|
|
Style: tcell.StyleDefault.Reverse(true),
|
|
|
|
})
|
2019-11-06 22:30:59 +03:00
|
|
|
searchHitDelta = -1
|
2019-07-06 14:33:41 +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
|
|
|
if stringIndexAtColumnZero >= len(line.Tokens()) {
|
2019-11-05 23:09:45 +03:00
|
|
|
// Nothing (more) to display, never mind
|
|
|
|
return returnMe
|
2019-07-06 14:33:41 +03:00
|
|
|
}
|
2019-06-29 19:29:37 +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
|
|
|
plain := line.Plain()
|
|
|
|
matchRanges := getMatchRanges(&plain, search)
|
|
|
|
for _, token := range line.Tokens()[stringIndexAtColumnZero:] {
|
2019-11-05 23:09:45 +03:00
|
|
|
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.
|
|
|
|
returnMe[len(returnMe)-1] = Token{
|
|
|
|
Rune: '>',
|
|
|
|
Style: tcell.StyleDefault.Reverse(true),
|
|
|
|
}
|
2019-08-03 21:14:22 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2019-06-29 19:29:37 +03:00
|
|
|
style := token.Style
|
2019-11-06 22:30:59 +03:00
|
|
|
if matchRanges.InRange(len(returnMe) + stringIndexAtColumnZero + searchHitDelta) {
|
2019-08-04 00:21:40 +03:00
|
|
|
// Search hits in reverse video
|
2019-06-29 19:29:37 +03:00
|
|
|
style = style.Reverse(true)
|
|
|
|
}
|
|
|
|
|
2019-11-05 23:09:45 +03:00
|
|
|
returnMe = append(returnMe, Token{
|
|
|
|
Rune: token.Rune,
|
|
|
|
Style: style,
|
|
|
|
})
|
2019-06-12 08:07:13 +03:00
|
|
|
}
|
2019-11-05 23:09:45 +03:00
|
|
|
|
|
|
|
return returnMe
|
2019-06-12 08:07:13 +03:00
|
|
|
}
|
|
|
|
|
2019-10-25 20:24:37 +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 {
|
2019-06-28 23:41:29 +03:00
|
|
|
p.screen.SetContent(pos, height-1, token, nil, tcell.StyleDefault)
|
|
|
|
pos++
|
|
|
|
}
|
2019-06-29 12:20:48 +03:00
|
|
|
|
2019-06-29 12:24:03 +03:00
|
|
|
// Add a cursor
|
|
|
|
p.screen.SetContent(pos, height-1, ' ', nil, tcell.StyleDefault.Reverse(true))
|
2019-06-28 23:41:29 +03:00
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
func (p *Pager) _AddLines(spinner string) {
|
2019-07-06 08:45:07 +03:00
|
|
|
_, height := p.screen.Size()
|
2019-06-12 22:55:09 +03:00
|
|
|
wantedLineCount := height - 1
|
2019-06-11 19:52:38 +03:00
|
|
|
|
2019-06-12 22:55:09 +03:00
|
|
|
lines := p.reader.GetLines(p.firstLineOneBased, wantedLineCount)
|
2019-06-12 08:07:13 +03:00
|
|
|
|
2019-06-12 22:55:09 +03:00
|
|
|
// If we're asking for past-the-end lines, the Reader will clip for us,
|
|
|
|
// and we should adapt to that. Otherwise if you scroll 100 lines past
|
|
|
|
// the end, you'll then have to scroll 100 lines up again before the
|
|
|
|
// display starts scrolling visibly.
|
|
|
|
p.firstLineOneBased = lines.firstLineOneBased
|
|
|
|
|
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
|
|
|
// Count the length of the last line number
|
|
|
|
//
|
|
|
|
// Offsets figured out through trial-and-error...
|
|
|
|
lastLineOneBased := lines.firstLineOneBased + len(lines.lines) - 1
|
2021-01-12 23:53:09 +03:00
|
|
|
numberPrefixLength := len(strconv.Itoa(lastLineOneBased)) + 1
|
|
|
|
if numberPrefixLength < 4 {
|
|
|
|
// 4 = space for 3 digits followed by one whitespace
|
|
|
|
//
|
|
|
|
// https://github.com/walles/moar/issues/38
|
|
|
|
numberPrefixLength = 4
|
|
|
|
}
|
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
|
|
|
|
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
|
|
|
if !p.showLineNumbers {
|
2021-01-12 23:53:09 +03:00
|
|
|
numberPrefixLength = 0
|
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-08-04 08:15:35 +03:00
|
|
|
screenLineNumber := 0
|
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
|
|
|
for i, line := range lines.lines {
|
|
|
|
lineNumber := p.firstLineOneBased + i
|
2021-01-12 23:53:09 +03:00
|
|
|
p._AddLine(&lineNumber, numberPrefixLength, screenLineNumber, line)
|
2019-08-04 08:15:35 +03:00
|
|
|
screenLineNumber++
|
2019-06-11 19:52:38 +03:00
|
|
|
}
|
2019-06-14 08:08:20 +03:00
|
|
|
|
2019-10-16 07:09:21 +03:00
|
|
|
eofSpinner := spinner
|
|
|
|
if eofSpinner == "" {
|
|
|
|
// This happens when we're done
|
|
|
|
eofSpinner = "---"
|
|
|
|
}
|
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
|
|
|
spinnerLine := NewLine(_EofMarkerFormat + eofSpinner)
|
|
|
|
p._AddLine(nil, 0, screenLineNumber, spinnerLine)
|
2019-08-04 08:15:35 +03:00
|
|
|
|
2019-07-06 08:45:07 +03:00
|
|
|
switch p.mode {
|
|
|
|
case _Searching:
|
2019-06-28 23:41:29 +03:00
|
|
|
p._AddSearchFooter()
|
2019-07-06 08:45:07 +03:00
|
|
|
|
|
|
|
case _NotFound:
|
|
|
|
p._SetFooter("Not found: " + p.searchString)
|
|
|
|
|
|
|
|
case _Viewing:
|
2020-09-05 21:31:21 +03:00
|
|
|
helpText := "Press ESC / q to exit, '/' to search, '?' for help"
|
2019-07-26 10:26:58 +03:00
|
|
|
if p.isShowingHelp {
|
|
|
|
helpText = "Press ESC / q to exit help, '/' to search"
|
|
|
|
}
|
2019-10-16 07:01:49 +03:00
|
|
|
p._SetFooter(lines.statusText + spinner + " " + helpText)
|
2019-07-06 08:45:07 +03:00
|
|
|
|
|
|
|
default:
|
2019-07-06 08:54:33 +03:00
|
|
|
panic(fmt.Sprint("Unsupported pager mode: ", p.mode))
|
2019-06-28 23:41:29 +03:00
|
|
|
}
|
2019-07-06 08:45:07 +03:00
|
|
|
}
|
|
|
|
|
2019-10-25 20:24:37 +03:00
|
|
|
func (p *Pager) _SetFooter(footer string) {
|
2019-07-06 08:45:07 +03:00
|
|
|
width, height := p.screen.Size()
|
2019-06-28 23:41:29 +03:00
|
|
|
|
2019-06-18 22:58:17 +03:00
|
|
|
pos := 0
|
|
|
|
footerStyle := tcell.StyleDefault.Reverse(true)
|
2019-07-06 08:45:07 +03:00
|
|
|
for _, token := range footer {
|
2019-06-18 22:58:17 +03:00
|
|
|
p.screen.SetContent(pos, height-1, token, nil, footerStyle)
|
|
|
|
pos++
|
2019-06-14 08:08:20 +03:00
|
|
|
}
|
|
|
|
|
2019-06-18 22:58:17 +03:00
|
|
|
for ; pos < width; pos++ {
|
|
|
|
p.screen.SetContent(pos, height-1, ' ', nil, footerStyle)
|
2019-06-14 08:08:20 +03:00
|
|
|
}
|
2019-06-12 08:07:13 +03:00
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
func (p *Pager) _Redraw(spinner string) {
|
2019-06-11 22:09:57 +03:00
|
|
|
p.screen.Clear()
|
2019-06-11 19:52:38 +03:00
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
p._AddLines(spinner)
|
2019-06-11 19:52:38 +03:00
|
|
|
|
2019-06-16 21:33:18 +03:00
|
|
|
p.screen.Show()
|
2019-06-11 19:52:38 +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
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
func (p *Pager) _ScrollToSearchHits() {
|
2019-06-30 23:16:04 +03:00
|
|
|
if p.searchPattern == nil {
|
|
|
|
// This is not a search
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
firstHitLine := p._FindFirstHitLineOneBased(p.firstLineOneBased, false)
|
2019-07-06 08:45:07 +03:00
|
|
|
if firstHitLine == nil {
|
|
|
|
// No match, give up
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if *firstHitLine <= p._GetLastVisibleLineOneBased() {
|
|
|
|
// Already on-screen, never mind
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p.firstLineOneBased = *firstHitLine
|
|
|
|
}
|
|
|
|
|
2019-10-25 20:24:37 +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
|
|
|
|
2019-11-27 20:43:46 +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
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +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
|
|
|
|
firstSearchLineOneBased = p._GetLastVisibleLineOneBased() + 1
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +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
|
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +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))
|
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +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
|
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +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
|
|
|
|
2019-11-27 20:43:46 +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]
|
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
func (p *Pager) _OnSearchKey(key tcell.Key) {
|
2019-06-28 23:41:29 +03:00
|
|
|
switch key {
|
|
|
|
case tcell.KeyEscape, tcell.KeyEnter:
|
2019-07-06 08:45:07 +03:00
|
|
|
p.mode = _Viewing
|
2019-06-28 23:41:29 +03:00
|
|
|
|
2019-06-29 12:20:48 +03:00
|
|
|
case tcell.KeyBackspace, tcell.KeyDEL:
|
|
|
|
if len(p.searchString) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-02 14:17:38 +03:00
|
|
|
p.searchString = removeLastChar(p.searchString)
|
2019-11-27 20:43:46 +03:00
|
|
|
p._UpdateSearchPattern()
|
2019-06-29 12:20:48 +03:00
|
|
|
|
2019-08-04 08:40:26 +03:00
|
|
|
case tcell.KeyUp:
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased--
|
|
|
|
p.mode = _Viewing
|
|
|
|
|
|
|
|
case tcell.KeyDown:
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased++
|
|
|
|
p.mode = _Viewing
|
|
|
|
|
|
|
|
case tcell.KeyPgUp:
|
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased -= (height - 1)
|
|
|
|
p.mode = _Viewing
|
|
|
|
|
|
|
|
case tcell.KeyPgDn:
|
|
|
|
_, 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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
func (p *Pager) _MoveRight(delta int) {
|
2019-11-19 13:41:56 +03:00
|
|
|
if p.showLineNumbers && delta > 0 {
|
|
|
|
p.showLineNumbers = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.leftColumnZeroBased == 0 && delta < 0 {
|
|
|
|
p.showLineNumbers = true
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
func (p *Pager) _OnKey(key tcell.Key) {
|
2019-11-28 20:14:09 +03:00
|
|
|
if key == tcell.KeyCtrlL {
|
|
|
|
// This is useful when we're piping in from something writing to both
|
|
|
|
// stdout and stderr.
|
|
|
|
p.screen.Sync()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-06 08:45:07 +03:00
|
|
|
if p.mode == _Searching {
|
2019-11-27 20:43:46 +03:00
|
|
|
p._OnSearchKey(key)
|
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
|
|
|
|
2019-06-11 22:28:21 +03:00
|
|
|
switch key {
|
2019-06-13 16:56:06 +03:00
|
|
|
case tcell.KeyEscape:
|
2019-06-15 09:10:56 +03:00
|
|
|
p.Quit()
|
2019-06-12 22:55:09 +03:00
|
|
|
|
|
|
|
case tcell.KeyUp:
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased--
|
|
|
|
|
2019-06-13 16:56:06 +03:00
|
|
|
case tcell.KeyDown, tcell.KeyEnter:
|
2019-06-12 22:55:09 +03:00
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased++
|
2019-06-13 07:14:41 +03:00
|
|
|
|
2019-07-06 14:33:41 +03:00
|
|
|
case tcell.KeyRight:
|
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
|
|
|
p._MoveRight(16)
|
2019-07-06 14:33:41 +03:00
|
|
|
|
|
|
|
case tcell.KeyLeft:
|
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
|
|
|
p._MoveRight(-16)
|
2019-07-06 08:45:07 +03:00
|
|
|
|
2019-06-13 07:14:41 +03:00
|
|
|
case tcell.KeyHome:
|
|
|
|
p.firstLineOneBased = 1
|
|
|
|
|
|
|
|
case tcell.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
|
|
|
|
2019-06-13 07:21:43 +03:00
|
|
|
case tcell.KeyPgDn:
|
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased += (height - 1)
|
|
|
|
|
|
|
|
case tcell.KeyPgUp:
|
|
|
|
_, height := p.screen.Size()
|
|
|
|
p.firstLineOneBased -= (height - 1)
|
2019-06-16 11:02:19 +03:00
|
|
|
|
|
|
|
default:
|
2019-12-06 21:36:31 +03:00
|
|
|
log.Debugf("Unhandled key event %v", key)
|
2019-06-11 22:28:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
func (p *Pager) _OnSearchRune(char rune) {
|
2019-06-29 12:20:48 +03:00
|
|
|
p.searchString = p.searchString + string(char)
|
2019-11-27 20:43:46 +03:00
|
|
|
p._UpdateSearchPattern()
|
2019-06-28 23:41:29 +03:00
|
|
|
}
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
func (p *Pager) _OnRune(char rune) {
|
2019-07-06 08:45:07 +03:00
|
|
|
if p.mode == _Searching {
|
2019-11-27 20:43:46 +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':
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased--
|
|
|
|
|
|
|
|
case 'j', 'e':
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased++
|
|
|
|
|
2020-09-05 21:31:21 +03:00
|
|
|
case 'l':
|
|
|
|
// vim right
|
|
|
|
p._MoveRight(16)
|
|
|
|
|
|
|
|
case 'h':
|
|
|
|
// vim left
|
|
|
|
p._MoveRight(-16)
|
|
|
|
|
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':
|
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: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':
|
2019-11-27 20:43:46 +03:00
|
|
|
p._ScrollToNextSearchHit()
|
2019-07-06 08:45:07 +03:00
|
|
|
|
2019-07-06 11:31:40 +03:00
|
|
|
case 'p', 'N':
|
2019-11-27 20:43:46 +03:00
|
|
|
p._ScrollToPreviousSearchHit()
|
2019-07-06 08:45:07 +03:00
|
|
|
|
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
|
2019-11-27 20:43:46 +03:00
|
|
|
func (p *Pager) StartPaging(screen tcell.Screen) {
|
2019-06-16 22:26:04 +03:00
|
|
|
// We want to match the terminal theme, see screen.Init() source code
|
|
|
|
os.Setenv("TCELL_TRUECOLOR", "disable")
|
|
|
|
|
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
|
|
|
if e := screen.Init(); e != nil {
|
2019-06-11 19:29:30 +03:00
|
|
|
fmt.Fprintf(os.Stderr, "%v\n", e)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2019-06-15 09:10:56 +03:00
|
|
|
p.screen = screen
|
2019-11-14 09:45:15 +03:00
|
|
|
screen.EnableMouse()
|
2019-06-15 09:10:56 +03:00
|
|
|
screen.Show()
|
2019-11-27 20:43:46 +03:00
|
|
|
p._Redraw("")
|
2019-06-11 19:29:30 +03:00
|
|
|
|
2019-07-11 19:52:20 +03:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
// Wait for new lines to appear
|
|
|
|
<-p.reader.moreLinesAdded
|
2019-07-15 08:08:04 +03:00
|
|
|
screen.PostEvent(tcell.NewEventInterrupt(nil))
|
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() {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
screen.PostEvent(tcell.NewEventInterrupt(spinnerFrames[spinnerIndex]))
|
|
|
|
spinnerIndex++
|
|
|
|
if spinnerIndex >= len(spinnerFrames) {
|
|
|
|
spinnerIndex = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Empty our spinner, loading done!
|
|
|
|
screen.PostEvent(tcell.NewEventInterrupt(""))
|
|
|
|
}()
|
|
|
|
|
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 {
|
|
|
|
ev := screen.PollEvent()
|
|
|
|
switch ev := ev.(type) {
|
|
|
|
case *tcell.EventKey:
|
|
|
|
if ev.Key() == tcell.KeyRune {
|
2019-11-27 20:43:46 +03:00
|
|
|
p._OnRune(ev.Rune())
|
2019-06-15 10:23:53 +03:00
|
|
|
} else {
|
2019-11-27 20:43:46 +03:00
|
|
|
p._OnKey(ev.Key())
|
2019-06-11 19:29:30 +03:00
|
|
|
}
|
2019-06-15 09:10:56 +03:00
|
|
|
|
2019-11-14 09:45:15 +03:00
|
|
|
case *tcell.EventMouse:
|
|
|
|
switch ev.Buttons() {
|
|
|
|
case tcell.WheelUp:
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased--
|
|
|
|
|
|
|
|
case tcell.WheelDown:
|
|
|
|
// Clipping is done in _AddLines()
|
|
|
|
p.firstLineOneBased++
|
2019-11-14 09:46:54 +03:00
|
|
|
|
|
|
|
case tcell.WheelRight:
|
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
|
|
|
p._MoveRight(16)
|
2019-11-14 09:46:54 +03:00
|
|
|
|
|
|
|
case tcell.WheelLeft:
|
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
|
|
|
p._MoveRight(-16)
|
2019-11-14 09:45:15 +03:00
|
|
|
}
|
|
|
|
|
2019-06-15 10:23:53 +03:00
|
|
|
case *tcell.EventResize:
|
|
|
|
// We'll be implicitly redrawn just by taking another lap in the loop
|
2019-06-16 11:02:19 +03:00
|
|
|
|
2019-07-15 00:18:26 +03:00
|
|
|
case *tcell.EventInterrupt:
|
|
|
|
// This means we got more lines, look for NewEventInterrupt higher up
|
|
|
|
// in this file. Doing nothing here is fine, the refresh happens after
|
|
|
|
// this switch statement.
|
2019-10-16 07:01:49 +03:00
|
|
|
data := ev.Data()
|
|
|
|
if data != nil {
|
|
|
|
// From: https://yourbasic.org/golang/interface-to-string/
|
|
|
|
spinner = fmt.Sprintf("%v", data)
|
|
|
|
}
|
2019-07-15 00:18:26 +03:00
|
|
|
|
2019-06-16 11:02:19 +03:00
|
|
|
default:
|
2019-12-06 21:36:31 +03:00
|
|
|
log.Warnf("Unhandled event type: %v", ev)
|
2019-06-11 19:29:30 +03:00
|
|
|
}
|
|
|
|
|
2019-06-22 00:24:53 +03:00
|
|
|
// FIXME: If more events are ready, skip this redraw, that
|
|
|
|
// should speed up mouse wheel scrolling
|
|
|
|
|
2019-11-27 20:43:46 +03:00
|
|
|
p._Redraw(spinner)
|
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
|
|
|
}
|