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:
parent
ecae4811de
commit
5ff126946a
227
m/pager.go
227
m/pager.go
@ -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:
|
||||
|
12
m/reader.go
12
m/reader.go
@ -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
131
m/screenLines.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user