back to being able to draw the screen

This commit is contained in:
Daisuke Maki 2016-05-27 12:56:28 +09:00
parent 7064a04efa
commit 39d36c41b8
7 changed files with 164 additions and 166 deletions

View file

@ -219,11 +219,12 @@ func doToggleRangeMode(ctx context.Context, state *Peco, _ termbox.Event) {
trace("doToggleRangeMode: START")
defer trace("doToggleRangeMode: END")
if state.RangeMode() {
state.SetSelectionRangeStart(invalidSelectionRange)
r := state.SelectionRangeStart()
if r.Valid() {
r.Reset()
} else {
cl := state.Location().LineNumber()
state.SetSelectionRangeStart(cl)
r.SetValue(cl)
if l, err := state.CurrentLineBuffer().LineAt(cl); err == nil {
state.selection.Add(l)
}
@ -231,7 +232,7 @@ func doToggleRangeMode(ctx context.Context, state *Peco, _ termbox.Event) {
}
func doCancelRangeMode(ctx context.Context, state *Peco, _ termbox.Event) {
state.SetSelectionRangeStart(invalidSelectionRange)
state.SelectionRangeStart().Reset()
}
func doSelectNone(ctx context.Context, state *Peco, _ termbox.Event) {
@ -298,7 +299,7 @@ func doCancel(ctx context.Context, state *Peco, e termbox.Event) {
return
}
if state.RangeMode() {
if state.SelectionRangeStart().Valid() {
doCancelRangeMode(ctx, state, e)
return
}

View file

@ -3,7 +3,6 @@ package peco
import (
"bufio"
"io"
"runtime"
"sync"
"time"
@ -163,78 +162,51 @@ func (rlb *RawLineBuffer) AppendLine(l Line) (Line, error) {
return rlb.Append(l)
}
func NewFilteredLineBuffer(src LineBuffer) *FilteredLineBuffer {
flb := &FilteredLineBuffer{
simplePipeline: simplePipeline{},
src: src,
selection: []int{},
func NewFilteredBuffer(src Buffer, page, perPage int) *FilteredBuffer {
fb := FilteredBuffer{
src: src,
}
src.Register(flb)
runtime.SetFinalizer(flb, func(x *FilteredLineBuffer) {
x.src.Unregister(x)
})
trace("src.Size = %d", src.Size())
s := perPage * (page - 1)
trace("s = %d", s)
if s > src.Size() {
return &fb
}
return flb
selection := make([]int, 0, src.Size())
e := s + perPage
if e >= src.Size() {
e = src.Size()
}
for i := s; i < e; i++ {
selection = append(selection, i)
}
fb.selection = selection
return &fb
}
func (flb *FilteredLineBuffer) Accept(p Pipeliner) {
cancelCh, incomingCh := p.Pipeline()
flb.cancelCh = cancelCh
flb.outputCh = make(chan Line)
go acceptPipeline(cancelCh, incomingCh, flb.outputCh,
&pipelineCtx{flb.Append, nil})
}
func (flb *FilteredLineBuffer) Append(l Line) (Line, error) {
func (flb *FilteredBuffer) Append(l Line) (Line, error) {
return l, nil
}
func (flb *FilteredLineBuffer) InvalidateUpTo(x int) {
p := -1
for i := 0; i < len(flb.selection); i++ {
if flb.selection[i] > x {
break
}
p = i
}
if p >= 0 {
flb.selection = append([]int(nil), flb.selection[p:]...)
}
for _, b := range flb.buffers {
b.InvalidateUpTo(p)
}
}
// LineAt returns the line at index `i`. Note that the i-th element
// in this filtered buffer may actually correspond to a totally
// different line number in the source buffer.
func (flb FilteredLineBuffer) LineAt(i int) (Line, error) {
if i < 0 || i >= len(flb.selection) {
func (flb FilteredBuffer) LineAt(i int) (Line, error) {
if i >= int(len(flb.selection)) {
return nil, ErrBufferOutOfRange
}
return flb.src.LineAt(flb.selection[i])
}
// Size returns the number of lines in the buffer
func (flb FilteredLineBuffer) Size() int {
func (flb FilteredBuffer) Size() int {
return len(flb.selection)
}
func (flb *FilteredLineBuffer) SelectSourceLineAt(i int) {
flb.selection = append(flb.selection, i)
}
func (flb *FilteredLineBuffer) Register(lb LineBuffer) {
flb.buffers.Register(lb)
}
func (flb *FilteredLineBuffer) Unregister(lb LineBuffer) {
flb.buffers.Unregister(lb)
}
// Buffer interface is used for containers for lines to be
// processed by peco.
type Buffer interface {
@ -259,7 +231,7 @@ func (mb MemoryBuffer) Size() int {
l.Lock()
defer l.Unlock()
return len(mb.lines)
return int(len(mb.lines))
}
func (mb MemoryBuffer) LineAt(n int) (Line, error) {
@ -267,7 +239,7 @@ func (mb MemoryBuffer) LineAt(n int) (Line, error) {
l.Lock()
defer l.Unlock()
if n > mb.Size() || n < 0 {
if n > mb.Size() {
return nil, errors.New("empty buffer")
}

View file

@ -89,7 +89,7 @@ type PagingRequest interface {
Type() PagingRequestType
}
type JumpToLineRequest uint
type JumpToLineRequest int
// Selection stores the line ids that were selected by the user.
// The contents of the Selection is always sorted from smallest to
@ -280,28 +280,14 @@ type RawLineBuffer struct {
onEnd func()
}
// FilteredLineBuffer holds a "filtered" buffer. It holds a reference to
// FilteredBuffer holds a "filtered" buffer. It holds a reference to
// the source buffer (note: should be immutable) and a list of indices
// into the source buffer
type FilteredLineBuffer struct {
simplePipeline
buffers dependentBuffers
src LineBuffer
// maps from our index to src's index
selection []int
type FilteredBuffer struct {
src Buffer
selection []int // maps from our index to src's index
}
/*
// Input handles input events from termbox.
type Input struct {
*Ctx
mutex sync.Locker // Currently only used for protecting Alt/Esc workaround
mod *time.Timer
keymap Keymap
currentKeySeq []string
}
*/
// Config holds all the data that can be configured in the
// external configuran file
type Config struct {

107
layout.go
View file

@ -2,6 +2,7 @@ package peco
import (
"fmt"
"strconv"
"time"
"unicode/utf8"
@ -10,7 +11,7 @@ import (
"github.com/pkg/errors"
)
var extraOffset = 0
var extraOffset int = 0
const (
DefaultLayoutType = LayoutTypeTopDown
@ -63,14 +64,14 @@ func printScreenWithOffset(x, y, xOffset int, fg, bg termbox.Attribute, msg stri
if c == '\t' {
// In case we found a tab, we draw it as 4 spaces
n := 4 - (x+xOffset)%4
for i := 0; i <= n; i++ {
screen.SetCell(x+i, y, ' ', fg, bg)
for i := int(0); i <= n; i++ {
screen.SetCell(int(x+i), int(y), ' ', fg, bg)
}
written += n
x += n
} else {
screen.SetCell(x, y, c, fg, bg)
n := runewidth.RuneWidth(c)
screen.SetCell(int(x), int(y), c, fg, bg)
n := int(runewidth.RuneWidth(c))
x += n
written += n
}
@ -81,10 +82,10 @@ func printScreenWithOffset(x, y, xOffset int, fg, bg termbox.Attribute, msg stri
}
width, _ := screen.Size()
for ; x < width; x++ {
screen.SetCell(x, y, ' ', fg, bg)
for ; x < int(width); x++ {
screen.SetCell(int(x), int(y), ' ', fg, bg)
}
written += width - x
written += int(width) - x
return written
}
@ -95,7 +96,10 @@ func NewAnchorSettings(anchor VerticalAnchor, offset int) *AnchorSettings {
panic("Invalid vertical anchor specified")
}
return &AnchorSettings{anchor, offset}
return &AnchorSettings{
anchor: anchor,
anchorOffset: offset,
}
}
// AnchorPosition returns the starting y-offset, based on the
@ -107,7 +111,7 @@ func (as AnchorSettings) AnchorPosition() int {
pos = as.anchorOffset
case AnchorBottom:
_, h := screen.Size()
pos = h - as.anchorOffset - 1 // -1 is required because y is 0 base, but h is 1 base
pos = int(h) - as.anchorOffset - 1 // -1 is required because y is 0 base, but h is 1 base
default:
panic("Unknown anchor type!")
}
@ -125,8 +129,8 @@ func NewUserPrompt(anchor VerticalAnchor, anchorOffset int, prompt string, style
return &UserPrompt{
AnchorSettings: &AnchorSettings{anchor, anchorOffset},
prompt: prompt,
promptLen: promptLen,
styles: styles,
promptLen: int(promptLen),
styles: styles,
}
}
@ -160,12 +164,15 @@ func (u UserPrompt) Draw(state *Peco) {
printScreen(u.promptLen+1, location, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false)
case c.Pos():
// the entire string + the caret after the string
printScreen(u.promptLen, location, fg, bg, "", true)
printScreen(u.promptLen+1, location, fg, bg, qs, false)
printScreen(u.promptLen+runewidth.StringWidth(qs)+1, location, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false)
pos := u.promptLen
printScreen(pos, location, fg, bg, "", true)
pos += 1
printScreen(pos, location, fg, bg, qs, false)
pos += int(runewidth.StringWidth(qs))
printScreen(pos, location, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false)
default:
// the caret is in the middle of the string
prev := 0
prev := int(0)
for i, r := range q.Runes() {
fg := u.styles.Query.fg
bg := u.styles.Query.bg
@ -173,8 +180,8 @@ func (u UserPrompt) Draw(state *Peco) {
fg |= termbox.AttrReverse
bg |= termbox.AttrReverse
}
screen.SetCell(u.promptLen+1+prev, location, r, fg, bg)
prev += runewidth.RuneWidth(r)
screen.SetCell(int(u.promptLen+1+prev), int(location), r, fg, bg)
prev += int(runewidth.RuneWidth(r))
}
fg := u.styles.Query.fg
bg := u.styles.Query.bg
@ -185,7 +192,7 @@ func (u UserPrompt) Draw(state *Peco) {
loc := state.Location()
pmsg := fmt.Sprintf("%s [%d (%d/%d)]", state.Filters().GetCurrent().String(), loc.Total(), loc.Page(), loc.MaxPage())
printScreen(width-runewidth.StringWidth(pmsg), location, u.styles.Basic.fg, u.styles.Basic.bg, pmsg, false)
printScreen(int(width-runewidth.StringWidth(pmsg)), location, u.styles.Basic.fg, u.styles.Basic.bg, pmsg, false)
screen.Flush()
}
@ -195,7 +202,7 @@ func NewStatusBar(anchor VerticalAnchor, anchorOffset int, styles *StyleSet) *St
return &StatusBar{
AnchorSettings: NewAnchorSettings(anchor, anchorOffset),
clearTimer: nil,
styles: styles,
styles: styles,
timerMutex: newMutex(),
}
}
@ -248,7 +255,7 @@ func (s *StatusBar) PrintStatus(msg string, clearDelay time.Duration) {
}
if width > 0 {
printScreen(w-width, location, fgAttr|termbox.AttrReverse|termbox.AttrBold|termbox.AttrReverse, bgAttr|termbox.AttrReverse, msg, false)
printScreen(int(w-width), location, fgAttr|termbox.AttrReverse|termbox.AttrBold|termbox.AttrReverse, bgAttr|termbox.AttrReverse, msg, false)
}
screen.Flush()
@ -266,11 +273,11 @@ func (s *StatusBar) PrintStatus(msg string, clearDelay time.Duration) {
// NewListArea creates a new ListArea struct
func NewListArea(anchor VerticalAnchor, anchorOffset int, sortTopDown bool, styles *StyleSet) *ListArea {
return &ListArea{
AnchorSettings: NewAnchorSettings(anchor, anchorOffset),
displayCache: []Line{},
dirty: false,
sortTopDown: sortTopDown,
styles: styles,
AnchorSettings: NewAnchorSettings(anchor, anchorOffset),
displayCache: []Line{},
dirty: false,
sortTopDown: sortTopDown,
styles: styles,
}
}
@ -295,8 +302,13 @@ func selectionContains(state *Peco, n int) bool {
// Draw displays the ListArea on the screen
func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bool) {
trace("ListArea.Draw: START")
defer trace("ListArea.Draw: END")
trace("START ListArea.Draw perPage = %d, runningQuery = %t", perPage, runningQuery)
defer trace("END ListArea.Draw")
if perPage < 1 {
panic("perPage < 1 (was " + strconv.Itoa(perPage) + ")")
}
loc := state.Location()
linebuf := state.CurrentLineBuffer()
@ -336,7 +348,7 @@ func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bo
// previously drawn lines are cached. first, truncate the cache
// to current size of the drawable area
if ldc := len(l.displayCache); ldc != perPage {
if ldc := int(len(l.displayCache)); ldc != perPage {
newCache := make([]Line, perPage)
copy(newCache, l.displayCache)
l.displayCache = newCache
@ -362,12 +374,12 @@ func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bo
var cached, written int
var fgAttr, bgAttr termbox.Attribute
for n := 0; n < perPage; n++ {
for n := int(0); n < perPage; n++ {
switch {
case n+loc.Offset() == loc.LineNumber():
fgAttr = l.styles.Selected.fg
bgAttr = l.styles.Selected.bg
case selectionContains(state, n + loc.Offset()):
case selectionContains(state, n+loc.Offset()):
fgAttr = l.styles.SavedSelection.fg
bgAttr = l.styles.SavedSelection.bg
default:
@ -400,13 +412,15 @@ func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bo
written++
l.displayCache[n] = target
x := -1 * loc.Column()
// XXX https://github.com/peco/peco/pull/249 this used to be
// x := -loc.Column(), may need to look at it again
x := int(0)
xOffset := loc.Column()
line := target.DisplayString()
if state.SingleKeyJumpMode() || state.SingleKeyJumpShowPrefix() {
prefixes := state.SingleKeyJumpPrefixes()
if n < len(prefixes) {
if n < int(len(prefixes)) {
printScreenWithOffset(x, y, xOffset, fgAttr|termbox.AttrBold|termbox.AttrReverse, bgAttr, string(prefixes[n]), false)
printScreenWithOffset(x+1, y, xOffset, fgAttr, bgAttr, " ", false)
} else {
@ -534,7 +548,11 @@ func linesPerPage() int {
// list area is always the display area - 2 lines for prompt and status
reservedLines := 2 + extraOffset
return height - reservedLines
pp := height - reservedLines
if pp < 1 {
panic("linesPerPage is < 1 (height = " +strconv.Itoa(height) +", reservedLines = " + strconv.Itoa(reservedLines) + ")")
}
return pp
}
// MovePage scrolls the screen
@ -616,21 +634,22 @@ func verticalScroll(state *Peco, l *BasicLayout, p PagingRequest) bool {
// if we were in range mode, we need to do stuff. otherwise
// just bail out
if !state.RangeMode() {
r := state.SelectionRangeStart()
if !r.Valid() {
return true
}
sel := state.Selection()
if l.list.sortTopDown {
if loc.LineNumber() < state.SelectionRangeStart() {
for lineno := loc.LineNumber(); lineno <= state.SelectionRangeStart(); lineno++ {
if loc.LineNumber() < r.Value() {
for lineno := loc.LineNumber(); lineno <= r.Value(); lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Add(line)
}
}
switch {
case state.SelectionRangeStart() <= lineBefore:
for lineno := state.SelectionRangeStart(); lineno <= lcur && lineno < lineBefore; lineno++ {
case r.Value() <= lineBefore:
for lineno := r.Value(); lineno <= lcur && lineno < lineBefore; lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Remove(line)
}
@ -643,15 +662,15 @@ func verticalScroll(state *Peco, l *BasicLayout, p PagingRequest) bool {
}
}
} else {
for lineno := state.SelectionRangeStart(); lineno <= lcur && lineno <= loc.LineNumber(); lineno++ {
for lineno := r.Value(); lineno <= lcur && lineno <= loc.LineNumber(); lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Add(line)
}
}
switch {
case lineBefore <= state.SelectionRangeStart():
for lineno := lineBefore; lineno < state.SelectionRangeStart(); lineno++ {
case lineBefore <= r.Value():
for lineno := lineBefore; lineno < r.Value(); lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Remove(line)
}
@ -674,9 +693,9 @@ func horizontalScroll(state *Peco, l *BasicLayout, p PagingRequest) bool {
width, _ := screen.Size()
loc := state.Location()
if p.Type() == ToScrollRight {
loc.SetColumn(loc.Column() + width / 2)
loc.SetColumn(loc.Column() + width/2)
} else if loc.Column() > 0 {
loc.SetColumn(loc.Column() - width / 2)
loc.SetColumn(loc.Column() - width/2)
if loc.Column() < 0 {
loc.SetColumn(0)
}

24
page.go
View file

@ -63,25 +63,9 @@ func (l Location) PageCrop() PageCrop {
}
}
// Crop returns a new LineBuffer whose contents are
// Crop returns a new Buffer whose contents are
// bound within the given range
func (pf PageCrop) Crop(in LineBuffer) LineBuffer {
out := &FilteredLineBuffer{
src: in,
selection: []int{},
}
s := pf.perPage * (pf.currentPage - 1)
e := s + pf.perPage
if s > in.Size() {
return out
}
if e >= in.Size() {
e = in.Size()
}
for i := s; i < e; i++ {
out.SelectSourceLineAt(i)
}
return out
func (pf PageCrop) Crop(in Buffer) Buffer {
trace("Cropping for page %d, %d entries per page", pf.currentPage, pf.perPage)
return NewFilteredBuffer(in, pf.currentPage, pf.perPage)
}

81
peco.go
View file

@ -64,7 +64,7 @@ type Peco struct {
queryExecTimer *time.Timer
resultCh chan Line
selection *Selection
selectionRangeStart int
selectionRangeStart RangeStart
singleKeyJumpMode bool
singleKeyJumpPrefixes []rune
singleKeyJumpShowPrefix bool
@ -84,9 +84,8 @@ type Peco struct {
func New() *Peco {
return &Peco{
activeLineBuffer: &MemoryBuffer{}, // XXX revisit this
selection: NewSelection(),
selectionRangeStart: invalidSelectionRange,
activeLineBuffer: &MemoryBuffer{}, // XXX revisit this
selection: NewSelection(),
}
}
@ -106,10 +105,6 @@ func (p Peco) Context() context.Context {
return p.ctx
}
func (p Peco) CurrentLineBuffer() LineBuffer {
return nil // XXX DUMMY
}
func (p Peco) LayoutType() string {
return p.layoutType
}
@ -130,18 +125,30 @@ func (p Peco) Selection() *Selection {
return p.selection
}
func (p Peco) SelectionRangeStart() int {
return p.selectionRangeStart
type RangeStart struct {
val int
valid bool
}
func (p Peco) SetSelectionRangeStart(s int) {
if s >= invalidSelectionRange {
p.selectionRangeStart = s
}
func (s RangeStart) Valid() bool {
return s.valid
}
func (p Peco) RangeMode() bool {
return p.selectionRangeStart != invalidSelectionRange
func (s RangeStart) Value() int {
return s.val
}
func (s *RangeStart) SetValue(n int) {
s.val = n
s.valid = true
}
func (s *RangeStart) Reset() {
s.valid = false
}
func (p Peco) SelectionRangeStart() *RangeStart {
return &p.selectionRangeStart
}
func (p Peco) SingleKeyJumpShowPrefix() bool {
@ -243,6 +250,11 @@ func (p *Peco) Run() error {
if err := p.Setup(); err != nil {
return errors.Wrap(err, "failed to setup peco")
}
// screen.Init must be called within Run() because we
// want to make sure to call screen.Close() after getting
// out of Run()
screen.Init()
defer screen.Close()
var ctx context.Context
var cancel func()
@ -342,18 +354,43 @@ func (p *Peco) ApplyConfig() error {
}
if err := p.populateCommandList(); err != nil {
return err
return errors.Wrap(err, "failed to populate command list")
}
if err := p.populateFilters(); err != nil {
return errors.Wrap(err, "failed to populate filters")
}
if err := p.populateKeymap(); err != nil {
return errors.Wrap(err, "failed to populate keymap")
}
return nil
}
func (p Peco) ActiveLineBuffer() Buffer {
func (p *Peco) populateFilters() error {
p.filters.Add(NewIgnoreCaseFilter())
p.filters.Add(NewCaseSensitiveFilter())
p.filters.Add(NewSmartCaseFilter())
p.filters.Add(NewRegexpFilter())
return nil
}
func (p *Peco) populateKeymap() error {
// Create a new keymap object
k := NewKeymap(p, p.config.Keymap, p.config.Action)
k.ApplyKeybinding()
p.keymap = k
return nil
}
func (p Peco) CurrentLineBuffer() Buffer {
return p.activeLineBuffer
}
func (p *Peco) ResetActiveLineBuffer() {
p.activeLineBuffer = p.source
p.Hub().SendDraw(false)
}
func (p *Peco) ExecQuery() bool {
@ -364,11 +401,9 @@ func (p *Peco) ExecQuery() bool {
// the raw source buffer
q := p.Query()
if q.Len() <= 0 {
if p.ActiveLineBuffer() != nil {
p.ResetActiveLineBuffer()
return true
}
return false
trace("empty query, reset buffer")
p.ResetActiveLineBuffer()
return true
}
delay := p.QueryExecDelay()

View file

@ -17,6 +17,7 @@ var (
)
func (t Termbox) Init() error {
trace("initializing termbox")
if err := termboxInit(); err != nil {
return err
}