1
1
mirror of https://github.com/walles/moar.git synced 2024-09-11 20:17:13 +03:00

Move pager lines rendering into its own file

This commit is contained in:
Johan Walles 2021-05-29 14:38:41 +02:00
parent ecae4811de
commit 5ff126946a
3 changed files with 202 additions and 168 deletions

View File

@ -117,70 +117,6 @@ func NewPager(r *Reader) *Pager {
}
}
func (p *Pager) addLine(fileLineNumber *int, numberPrefixLength int, screenLineNumber int, cells []twin.Cell) {
screenWidth, _ := p.screen.Size()
lineNumberString := ""
if numberPrefixLength > 0 && fileLineNumber != nil {
lineNumberString = formatNumber(uint(*fileLineNumber))
lineNumberString = fmt.Sprintf("%*s", numberPrefixLength-1, lineNumberString)
if len(lineNumberString) > numberPrefixLength {
panic(fmt.Errorf(
"lineNumberString <%s> longer than numberPrefixLength %d",
lineNumberString, numberPrefixLength))
}
}
for column, digit := range lineNumberString {
if column >= numberPrefixLength {
break
}
p.screen.SetCell(column, screenLineNumber, twin.NewCell(digit, _numberStyle))
}
screenCells := createScreenLine(p.leftColumnZeroBased, screenWidth-numberPrefixLength, cells)
for column, token := range screenCells {
p.screen.SetCell(column+numberPrefixLength, screenLineNumber, token)
}
}
func createScreenLine(
stringIndexAtColumnZero int,
screenColumnsCount int,
cells []twin.Cell,
) []twin.Cell {
var returnMe []twin.Cell
if stringIndexAtColumnZero > 0 {
// Indicate that it's possible to scroll left
returnMe = append(returnMe, twin.Cell{
Rune: '<',
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
})
}
if stringIndexAtColumnZero >= len(cells) {
// Nothing (more) to display, never mind
return returnMe
}
for _, cell := range cells[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.
returnMe[len(returnMe)-1] = twin.Cell{
Rune: '>',
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
}
break
}
returnMe = append(returnMe, cell)
}
return returnMe
}
func (p *Pager) _AddSearchFooter() {
_, height := p.screen.Size()
@ -194,95 +130,6 @@ func (p *Pager) _AddSearchFooter() {
p.screen.SetCell(pos, height-1, twin.NewCell(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
}
func (p *Pager) _AddLines(spinner string) {
width, height := p.screen.Size()
wantedLineCount := height - 1
lines := p.reader.GetLines(p.firstLineOneBased, wantedLineCount)
// 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
// Count the length of the last line number
//
// Offsets figured out through trial-and-error...
lastLineOneBased := lines.firstLineOneBased + len(lines.lines) - 1
numberPrefixLength := len(formatNumber(uint(lastLineOneBased))) + 1
if numberPrefixLength < 4 {
// 4 = space for 3 digits followed by one whitespace
//
// https://github.com/walles/moar/issues/38
numberPrefixLength = 4
}
if !p.ShowLineNumbers {
numberPrefixLength = 0
}
screenLineNumber := 0
screenFull := false
for lineIndex, line := range lines.lines {
lineNumber := p.firstLineOneBased + lineIndex
highlighted := line.HighlightedTokens(p.searchPattern)
var wrapped [][]twin.Cell
if p.WrapLongLines {
wrapped = wrapLine(width-numberPrefixLength, highlighted)
} else {
// All on one line
wrapped = [][]twin.Cell{highlighted}
}
for wrapIndex, linePart := range wrapped {
visibleLineNumber := &lineNumber
if wrapIndex > 0 {
visibleLineNumber = nil
}
p.addLine(visibleLineNumber, numberPrefixLength, screenLineNumber, linePart)
screenLineNumber++
if screenLineNumber >= height-1 {
// We have shown all the lines that can fit on the screen
screenFull = true
break
}
}
if screenFull {
break
}
}
eofSpinner := spinner
if eofSpinner == "" {
// This happens when we're done
eofSpinner = "---"
}
spinnerLine := cellsFromString(_EofMarkerFormat + eofSpinner)
p.addLine(nil, 0, screenLineNumber, spinnerLine)
switch p.mode {
case _Searching:
p._AddSearchFooter()
case _NotFound:
p._SetFooter("Not found: " + p.searchString)
case _Viewing:
helpText := "Press ESC / q to exit, '/' to search, '?' for help"
if p.isShowingHelp {
helpText = "Press ESC / q to exit help, '/' to search"
}
p._SetFooter(lines.statusText + spinner + " " + helpText)
default:
panic(fmt.Sprint("Unsupported pager mode: ", p.mode))
}
}
func (p *Pager) _SetFooter(footer string) {
width, height := p.screen.Size()
@ -301,7 +148,63 @@ func (p *Pager) _SetFooter(footer string) {
func (p *Pager) _Redraw(spinner string) {
p.screen.Clear()
p._AddLines(spinner)
width, height := p.screen.Size()
wantedLineCount := height - 1
inputLines := p.reader.GetLines(p.firstLineOneBased, wantedLineCount)
screenLines := ScreenLines{
inputLines: inputLines,
firstInputLineOneBased: p.firstLineOneBased,
leftColumnZeroBased: p.leftColumnZeroBased,
width: width,
height: wantedLineCount,
showLineNumbers: p.ShowLineNumbers,
wrapLongLines: p.WrapLongLines,
}
// 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 = screenLines.firstLineOneBased()
var screenLineNumber int
for lineNumber, row := range screenLines.getScreenLines(p.searchPattern) {
screenLineNumber = lineNumber
for column, cell := range row {
p.screen.SetCell(column, screenLineNumber, cell)
}
}
eofSpinner := spinner
if eofSpinner == "" {
// This happens when we're done
eofSpinner = "---"
}
spinnerLine := cellsFromString(_EofMarkerFormat + eofSpinner)
for column, cell := range spinnerLine {
p.screen.SetCell(column, screenLineNumber+1, cell)
}
switch p.mode {
case _Searching:
p._AddSearchFooter()
case _NotFound:
p._SetFooter("Not found: " + p.searchString)
case _Viewing:
helpText := "Press ESC / q to exit, '/' to search, '?' for help"
if p.isShowingHelp {
helpText = "Press ESC / q to exit help, '/' to search"
}
p._SetFooter(inputLines.statusText + spinner + " " + helpText)
default:
panic(fmt.Sprint("Unsupported pager mode: ", p.mode))
}
p.screen.Show()
}
@ -519,12 +422,12 @@ func (p *Pager) _OnSearchKey(key twin.KeyCode) {
p._UpdateSearchPattern()
case twin.KeyUp:
// Clipping is done in _AddLines()
// Clipping is done in _Redraw()
p.firstLineOneBased--
p.mode = _Viewing
case twin.KeyDown:
// Clipping is done in _AddLines()
// Clipping is done in _Redraw()
p.firstLineOneBased++
p.mode = _Viewing
@ -579,11 +482,11 @@ func (p *Pager) _OnKey(keyCode twin.KeyCode) {
p.Quit()
case twin.KeyUp:
// Clipping is done in _AddLines()
// Clipping is done in _Redraw()
p.firstLineOneBased--
case twin.KeyDown, twin.KeyEnter:
// Clipping is done in _AddLines()
// Clipping is done in _Redraw()
p.firstLineOneBased++
case twin.KeyRight:
@ -643,11 +546,11 @@ func (p *Pager) _OnRune(char rune) {
}
case 'k', 'y':
// Clipping is done in _AddLines()
// Clipping is done in _Redraw()
p.firstLineOneBased--
case 'j', 'e':
// Clipping is done in _AddLines()
// Clipping is done in _Redraw()
p.firstLineOneBased++
case 'l':
@ -776,11 +679,11 @@ func (p *Pager) StartPaging(screen twin.Screen) {
log.Tracef("Handling mouse event %d...", event.Buttons())
switch event.Buttons() {
case twin.MouseWheelUp:
// Clipping is done in _AddLines()
// Clipping is done in _Redraw()
p.firstLineOneBased--
case twin.MouseWheelDown:
// Clipping is done in _AddLines()
// Clipping is done in _Redraw()
p.firstLineOneBased++
case twin.MouseWheelLeft:

View File

@ -37,8 +37,8 @@ type Reader struct {
moreLinesAdded chan bool
}
// Lines contains a number of lines from the reader, plus metadata
type Lines struct {
// InputLines contains a number of lines from the reader, plus metadata
type InputLines struct {
lines []*Line
// One-based line number of the first line returned
@ -489,19 +489,19 @@ func (r *Reader) GetLine(lineNumberOneBased int) *Line {
}
// GetLines gets the indicated lines from the input
func (r *Reader) GetLines(firstLineOneBased int, wantedLineCount int) *Lines {
func (r *Reader) GetLines(firstLineOneBased int, wantedLineCount int) *InputLines {
r.lock.Lock()
defer r.lock.Unlock()
return r._GetLinesUnlocked(firstLineOneBased, wantedLineCount)
}
func (r *Reader) _GetLinesUnlocked(firstLineOneBased int, wantedLineCount int) *Lines {
func (r *Reader) _GetLinesUnlocked(firstLineOneBased int, wantedLineCount int) *InputLines {
if firstLineOneBased < 1 {
firstLineOneBased = 1
}
if len(r.lines) == 0 {
return &Lines{
return &InputLines{
lines: nil,
// The line number set here won't matter, we'll clip it anyway when we get it back
@ -530,7 +530,7 @@ func (r *Reader) _GetLinesUnlocked(firstLineOneBased int, wantedLineCount int) *
return r._GetLinesUnlocked(firstLineOneBased, wantedLineCount)
}
return &Lines{
return &InputLines{
lines: r.lines[firstLineZeroBased : lastLineZeroBased+1],
firstLineOneBased: firstLineOneBased,
statusText: r._CreateStatusUnlocked(firstLineOneBased, lastLineZeroBased+1),

131
m/screenLines.go Normal file
View File

@ -0,0 +1,131 @@
package m
import (
"fmt"
"regexp"
"github.com/walles/moar/twin"
)
type ScreenLines struct {
inputLines *InputLines
firstInputLineOneBased int
leftColumnZeroBased int
width int
height int
showLineNumbers bool
wrapLongLines bool
}
func (sl *ScreenLines) getScreenLines(searchPattern *regexp.Regexp) [][]twin.Cell {
// Count the length of the last line number
//
// Offsets figured out through trial-and-error...
lastLineOneBased := sl.inputLines.firstLineOneBased + len(sl.inputLines.lines) - 1
numberPrefixLength := len(formatNumber(uint(lastLineOneBased))) + 1
if numberPrefixLength < 4 {
// 4 = space for 3 digits followed by one whitespace
//
// https://github.com/walles/moar/issues/38
numberPrefixLength = 4
}
if !sl.showLineNumbers {
numberPrefixLength = 0
}
returnLines := make([][]twin.Cell, 0, sl.height)
screenFull := false
for lineIndex, line := range sl.inputLines.lines {
lineNumber := sl.firstLineOneBased() + lineIndex
highlighted := line.HighlightedTokens(searchPattern)
var wrapped [][]twin.Cell
if sl.wrapLongLines {
wrapped = wrapLine(sl.width-numberPrefixLength, highlighted)
} else {
// All on one line
wrapped = [][]twin.Cell{highlighted}
}
for wrapIndex, inputLinePart := range wrapped {
visibleLineNumber := &lineNumber
if wrapIndex > 0 {
visibleLineNumber = nil
}
newLine := make([]twin.Cell, 0, sl.width)
newLine = append(newLine, createLineNumberPrefix(visibleLineNumber, numberPrefixLength)...)
newLine = append(newLine, inputLinePart...)
if sl.leftColumnZeroBased > 0 && len(inputLinePart) > 0 {
// Add can-scroll-left marker
newLine[0] = twin.Cell{
Rune: '<',
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
}
}
if len(inputLinePart)+numberPrefixLength > sl.width {
newLine[sl.width-1] = twin.Cell{
Rune: '>',
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
}
}
returnLines = append(returnLines, newLine)
if len(returnLines) >= sl.height {
// We have shown all the lines that can fit on the screen
screenFull = true
break
}
}
if screenFull {
break
}
}
return returnLines
}
// Generate a line number prefix. Can be empty or all-whitespace depending on parameters.
func createLineNumberPrefix(fileLineNumber *int, numberPrefixLength int) []twin.Cell {
if numberPrefixLength == 0 {
return []twin.Cell{}
}
lineNumberPrefix := make([]twin.Cell, 0, numberPrefixLength)
if fileLineNumber == nil {
for len(lineNumberPrefix) < numberPrefixLength {
lineNumberPrefix = append(lineNumberPrefix, twin.Cell{Rune: ' '})
}
return lineNumberPrefix
}
lineNumberString := formatNumber(uint(*fileLineNumber))
lineNumberString = fmt.Sprintf("%*s ", numberPrefixLength-1, lineNumberString)
if len(lineNumberString) > numberPrefixLength {
panic(fmt.Errorf(
"lineNumberString <%s> longer than numberPrefixLength %d",
lineNumberString, numberPrefixLength))
}
for column, digit := range lineNumberString {
if column >= numberPrefixLength {
break
}
lineNumberPrefix = append(lineNumberPrefix, twin.NewCell(digit, _numberStyle))
}
return lineNumberPrefix
}
func (sl *ScreenLines) firstLineOneBased() int {
// FIXME: This is wrong when wrapping is enabled
return sl.inputLines.firstLineOneBased
}