More refactoring around current position/location state

This commit is contained in:
Daisuke Maki 2016-05-26 21:56:13 +09:00
parent 406cfe3bd7
commit 417be36cfb
9 changed files with 275 additions and 200 deletions

View file

@ -202,7 +202,7 @@ func doBackToInitialFilter(ctx context.Context, state *Peco, e termbox.Event) {
}
func doToggleSelection(ctx context.Context, state *Peco, _ termbox.Event) {
l, err := state.CurrentLineBuffer().LineAt(state.CurrentLine())
l, err := state.CurrentLineBuffer().LineAt(state.Location().LineNumber())
if err != nil {
return
}
@ -222,7 +222,7 @@ func doToggleRangeMode(ctx context.Context, state *Peco, _ termbox.Event) {
if state.RangeMode() {
state.SetSelectionRangeStart(invalidSelectionRange)
} else {
cl := state.CurrentLine()
cl := state.Location().LineNumber()
state.SetSelectionRangeStart(cl)
if l, err := state.CurrentLineBuffer().LineAt(cl); err == nil {
state.selection.Add(l)
@ -258,8 +258,8 @@ func doSelectVisible(ctx context.Context, state *Peco, _ termbox.Event) {
b := state.CurrentLineBuffer()
selection := state.Selection()
pi := state.PageInfo()
pc := PageCrop{pi.PerPage(), pi.Page()}
loc := state.Location()
pc := loc.PageCrop()
lb := pc.Crop(b)
for x := 0; x < lb.Size(); x++ {
l, err := lb.LineAt(x)
@ -279,7 +279,7 @@ func doFinish(ctx context.Context, state *Peco, _ termbox.Event) {
selection := state.Selection()
// Must end with all the selected lines.
if selection.Len() == 0 {
if l, err := state.CurrentLineBuffer().LineAt(state.CurrentLine()); err == nil {
if l, err := state.CurrentLineBuffer().LineAt(state.Location().LineNumber()); err == nil {
selection.Add(l)
}
}
@ -682,7 +682,7 @@ func makeCommandAction(cc *CommandConfig) ActionFunc {
return func(ctx context.Context, state *Peco, _ termbox.Event) {
sel := state.Selection()
if sel.Len() == 0 {
if l, err := state.CurrentLineBuffer().LineAt(state.CurrentLine()); err == nil {
if l, err := state.CurrentLineBuffer().LineAt(state.Location().LineNumber()); err == nil {
sel.Add(l)
}
}

View file

@ -5,6 +5,7 @@ import (
"io"
"runtime"
"sync"
"time"
"golang.org/x/net/context"
@ -297,11 +298,37 @@ func NewSource(in io.Reader, enableSep bool) *Source {
}
// Setup reads from the input os.File.
func (s *Source) Setup() {
func (s *Source) Setup(state *Peco) {
s.setupOnce.Do(func() {
l := s.locker()
l.Lock()
defer l.Unlock()
l := s.locker()
l.Lock()
defer l.Unlock()
done := make(chan struct{})
refresh := make(chan struct{})
defer close(done)
defer close(refresh)
go func() {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if _, ok := <-refresh; ok {
// Not a great thing to do, allowing nil to be passed
// as state, but for testing I couldn't come up with anything
// better for the moment
if state != nil && !state.ExecQuery() {
state.Hub().SendDraw(false)
}
}
case <-done:
return
}
}
}()
// This sync.Once var is used to receive the notification
// that there was at least 1 line read from the source
@ -315,6 +342,8 @@ func (s *Source) Setup() {
for scanner.Scan() {
s.lines = append(s.lines, NewRawLine(scanner.Text(), s.enableSep))
notify.Do(notifycb)
refresh <- struct{}{}
}
})
}

View file

@ -160,36 +160,28 @@ type AnchorSettings struct {
// UserPrompt draws the prompt line
type UserPrompt struct {
*Ctx
*AnchorSettings
prefix string
prefixLen int
basicStyle Style
queryStyle Style
prompt string
promptLen int
styles *StyleSet
}
// StatusBar draws the status message bar
type StatusBar struct {
*Ctx
*AnchorSettings
clearTimer *time.Timer
styles *StyleSet
timerMutex sync.Locker
basicStyle Style
}
// ListArea represents the area where the actual line buffer is
// displayed in the screen
type ListArea struct {
*Ctx
*AnchorSettings
sortTopDown bool
displayCache []Line
dirty bool
basicStyle Style
queryStyle Style
matchedStyle Style
selectedStyle Style
savedSelectionStyle Style
sortTopDown bool
displayCache []Line
dirty bool
styles *StyleSet
}
// BasicLayout is... the basic layout :) At this point this is the
@ -197,7 +189,6 @@ type ListArea struct {
// of components may be configurable, the actual types of components
// that are used are set and static
type BasicLayout struct {
*Ctx
*StatusBar
prompt *UserPrompt
list *ListArea
@ -416,12 +407,14 @@ type QueryFilterer interface {
String() string
}
type PageInfo struct {
page int
offset int
perPage int
total int
type Location struct {
col int
lineno int
maxPage int
page int
perPage int
offset int
total int
}
type Query struct {
@ -459,7 +452,6 @@ type Ctx struct {
mutex sync.Locker
currentLine int
currentCol int
currentPage *PageInfo
selection *Selection
activeLineBuffer LineBuffer
rawLineBuffer *RawLineBuffer

244
layout.go
View file

@ -7,6 +7,7 @@ import (
"github.com/mattn/go-runewidth"
"github.com/nsf/termbox-go"
"github.com/pkg/errors"
)
var extraOffset = 0
@ -115,20 +116,17 @@ func (as AnchorSettings) AnchorPosition() int {
}
// NewUserPrompt creates a new UserPrompt struct
func NewUserPrompt(ctx *Ctx, anchor VerticalAnchor, anchorOffset int) *UserPrompt {
prefix := ctx.config.Prompt
if len(prefix) <= 0 { // default
prefix = "QUERY>"
func NewUserPrompt(anchor VerticalAnchor, anchorOffset int, prompt string, styles *StyleSet) *UserPrompt {
if len(prompt) <= 0 { // default
prompt = "QUERY>"
}
prefixLen := runewidth.StringWidth(prefix)
promptLen := runewidth.StringWidth(prompt)
return &UserPrompt{
Ctx: ctx,
AnchorSettings: &AnchorSettings{anchor, anchorOffset},
prefix: prefix,
prefixLen: prefixLen,
basicStyle: ctx.config.Style.Basic,
queryStyle: ctx.config.Style.Query,
prompt: prompt,
promptLen: promptLen,
styles: styles,
}
}
@ -140,7 +138,7 @@ func (u UserPrompt) Draw(state *Peco) {
location := u.AnchorPosition()
// print "QUERY>"
printScreen(0, location, u.basicStyle.fg, u.basicStyle.bg, u.prefix, false)
printScreen(0, location, u.styles.Basic.fg, u.styles.Basic.bg, u.prompt, false)
c := state.Caret()
if c.Pos() <= 0 { // XXX Do we really need this?
@ -154,51 +152,51 @@ func (u UserPrompt) Draw(state *Peco) {
c.SetPos(ql)
}
fg := u.queryStyle.fg
bg := u.queryStyle.bg
fg := u.styles.Query.fg
bg := u.styles.Query.bg
switch ql {
case 0:
printScreen(u.prefixLen, location, fg, bg, "", true)
printScreen(u.prefixLen+1, location, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false)
printScreen(u.promptLen, location, fg, bg, "", true)
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.prefixLen, location, fg, bg, "", true)
printScreen(u.prefixLen+1, location, fg, bg, qs, false)
printScreen(u.prefixLen+runewidth.StringWidth(qs)+1, location, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false)
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)
default:
// the caret is in the middle of the string
prev := 0
for i, r := range q.Runes() {
fg := u.queryStyle.fg
bg := u.queryStyle.bg
fg := u.styles.Query.fg
bg := u.styles.Query.bg
if i == c.Pos() {
fg |= termbox.AttrReverse
bg |= termbox.AttrReverse
}
screen.SetCell(u.prefixLen+1+prev, location, r, fg, bg)
screen.SetCell(u.promptLen+1+prev, location, r, fg, bg)
prev += runewidth.RuneWidth(r)
}
fg := u.queryStyle.fg
bg := u.queryStyle.bg
printScreen(u.prefixLen+prev+1, location, fg, bg, "", true)
fg := u.styles.Query.fg
bg := u.styles.Query.bg
printScreen(u.promptLen+prev+1, location, fg, bg, "", true)
}
width, _ := screen.Size()
pmsg := fmt.Sprintf("%s [%d (%d/%d)]", state.Filters().GetCurrent().String(), u.currentPage.total, u.currentPage.page, u.currentPage.maxPage)
printScreen(width-runewidth.StringWidth(pmsg), location, u.basicStyle.fg, u.basicStyle.bg, pmsg, false)
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)
screen.Flush()
}
// NewStatusBar creates a new StatusBar struct
func NewStatusBar(ctx *Ctx, anchor VerticalAnchor, anchorOffset int) *StatusBar {
func NewStatusBar(anchor VerticalAnchor, anchorOffset int, styles *StyleSet) *StatusBar {
return &StatusBar{
Ctx: ctx,
AnchorSettings: NewAnchorSettings(anchor, anchorOffset),
clearTimer: nil,
styles: styles,
timerMutex: newMutex(),
basicStyle: ctx.config.Style.Basic,
}
}
@ -242,8 +240,8 @@ func (s *StatusBar) PrintStatus(msg string, clearDelay time.Duration) {
}
}
fgAttr := s.basicStyle.fg
bgAttr := s.basicStyle.bg
fgAttr := s.styles.Basic.fg
bgAttr := s.styles.Basic.bg
if w > width {
printScreen(0, location, fgAttr, bgAttr, string(pad), false)
@ -266,18 +264,13 @@ func (s *StatusBar) PrintStatus(msg string, clearDelay time.Duration) {
}
// NewListArea creates a new ListArea struct
func NewListArea(ctx *Ctx, anchor VerticalAnchor, anchorOffset int, sortTopDown bool) *ListArea {
func NewListArea(anchor VerticalAnchor, anchorOffset int, sortTopDown bool, styles *StyleSet) *ListArea {
return &ListArea{
Ctx: ctx,
AnchorSettings: NewAnchorSettings(anchor, anchorOffset),
sortTopDown: sortTopDown,
displayCache: []Line{},
dirty: false,
basicStyle: ctx.config.Style.Basic,
queryStyle: ctx.config.Style.Query,
matchedStyle: ctx.config.Style.Matched,
selectedStyle: ctx.config.Style.Selected,
savedSelectionStyle: ctx.config.Style.SavedSelection,
sortTopDown: sortTopDown,
styles: styles,
}
}
@ -304,7 +297,7 @@ func selectionContains(state *Peco, n int) bool {
func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bool) {
trace("ListArea.Draw: START")
defer trace("ListArea.Draw: END")
currentPage := l.currentPage
loc := state.Location()
linebuf := state.CurrentLineBuffer()
@ -315,30 +308,30 @@ func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bo
// to fill that many pages in the buffer
if runningQuery {
bufsiz := linebuf.Size()
page := currentPage.page
page := loc.Page()
for page > 1 {
if (currentPage.perPage*(page-1) < bufsiz) &&
(currentPage.perPage*page) >= bufsiz {
if (loc.PerPage()*(page-1) < bufsiz) &&
(loc.PerPage()*page) >= bufsiz {
break
}
page--
}
if currentPage.page != page {
currentPage.page = page
if loc.Page() != page {
loc.SetPage(page)
parent.DrawPrompt(state)
}
}
pf := PageCrop{perPage: currentPage.perPage, currentPage: currentPage.page}
pf := loc.PageCrop()
buf := pf.Crop(linebuf)
bufsiz := buf.Size()
// This protects us from losing the selected line in case our selected
// line is greater than the buffer
if lbufsiz := linebuf.Size(); lbufsiz > 0 && l.currentLine >= lbufsiz {
state.SetCurrentLine(lbufsiz - 1)
if lbufsiz := linebuf.Size(); lbufsiz > 0 && loc.LineNumber() >= lbufsiz {
loc.SetLineNumber(lbufsiz - 1)
}
// previously drawn lines are cached. first, truncate the cache
@ -364,22 +357,22 @@ func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bo
}
trace("ListArea.Draw: clearing row %d", y)
printScreen(0, y, l.basicStyle.fg, l.basicStyle.bg, "", true)
printScreen(0, y, l.styles.Basic.fg, l.styles.Basic.bg, "", true)
}
var cached, written int
var fgAttr, bgAttr termbox.Attribute
for n := 0; n < perPage; n++ {
switch {
case n+currentPage.offset == l.currentLine:
fgAttr = l.selectedStyle.fg
bgAttr = l.selectedStyle.bg
case selectionContains(state, n + currentPage.offset):
fgAttr = l.savedSelectionStyle.fg
bgAttr = l.savedSelectionStyle.bg
case n+loc.Offset() == loc.LineNumber():
fgAttr = l.styles.Selected.fg
bgAttr = l.styles.Selected.bg
case selectionContains(state, n + loc.Offset()):
fgAttr = l.styles.SavedSelection.fg
bgAttr = l.styles.SavedSelection.bg
default:
fgAttr = l.basicStyle.fg
bgAttr = l.basicStyle.bg
fgAttr = l.styles.Basic.fg
bgAttr = l.styles.Basic.bg
}
if n >= bufsiz {
@ -407,13 +400,14 @@ func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bo
written++
l.displayCache[n] = target
x := -l.currentCol
xOffset := l.currentCol
x := -1 * loc.Column()
xOffset := loc.Column()
line := target.DisplayString()
if state.SingleKeyJumpMode() || l.config.SingleKeyJump.ShowPrefix {
if n < len(l.config.SingleKeyJump.PrefixList) {
printScreenWithOffset(x, y, xOffset, fgAttr|termbox.AttrBold|termbox.AttrReverse, bgAttr, fmt.Sprintf("%c", l.config.SingleKeyJump.PrefixList[n]), false)
if state.SingleKeyJumpMode() || state.SingleKeyJumpShowPrefix() {
prefixes := state.SingleKeyJumpPrefixes()
if n < 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 {
printScreenWithOffset(x, y, xOffset, fgAttr, bgAttr, " ", false)
@ -440,14 +434,14 @@ func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bo
}
c := line[m[0]:m[1]]
n := printScreenWithOffset(prev, y, xOffset, l.matchedStyle.fg, mergeAttribute(bgAttr, l.matchedStyle.bg), c, true)
n := printScreenWithOffset(prev, y, xOffset, l.styles.Matched.fg, mergeAttribute(bgAttr, l.styles.Matched.bg), c, true)
prev += n
index += len(c)
}
m := matches[len(matches)-1]
if m[0] > index {
printScreenWithOffset(prev, y, xOffset, l.queryStyle.fg, mergeAttribute(bgAttr, l.queryStyle.bg), line[m[0]:m[1]], true)
printScreenWithOffset(prev, y, xOffset, l.styles.Query.fg, mergeAttribute(bgAttr, l.styles.Query.bg), line[m[0]:m[1]], true)
} else if len(line) > m[1] {
printScreenWithOffset(prev, y, xOffset, fgAttr, bgAttr, line[m[1]:len(line)], true)
}
@ -457,28 +451,26 @@ func (l *ListArea) Draw(state *Peco, parent Layout, perPage int, runningQuery bo
}
// NewDefaultLayout creates a new Layout in the default format (top-down)
func NewDefaultLayout(ctx *Ctx) *BasicLayout {
func NewDefaultLayout(state *Peco) *BasicLayout {
return &BasicLayout{
Ctx: ctx,
StatusBar: NewStatusBar(ctx, AnchorBottom, 0+extraOffset),
StatusBar: NewStatusBar(AnchorBottom, 0+extraOffset, state.Styles()),
// The prompt is at the top
prompt: NewUserPrompt(ctx, AnchorTop, 0),
prompt: NewUserPrompt(AnchorTop, 0, state.Prompt(), state.Styles()),
// The list area is at the top, after the prompt
// It's also displayed top-to-bottom order
list: NewListArea(ctx, AnchorTop, 1, true),
list: NewListArea(AnchorTop, 1, true, state.Styles()),
}
}
// NewBottomUpLayout creates a new Layout in bottom-up format
func NewBottomUpLayout(ctx *Ctx) *BasicLayout {
func NewBottomUpLayout(state *Peco) *BasicLayout {
return &BasicLayout{
Ctx: ctx,
StatusBar: NewStatusBar(ctx, AnchorBottom, 0+extraOffset),
StatusBar: NewStatusBar(AnchorBottom, 0+extraOffset, state.Styles()),
// The prompt is at the bottom, above the status bar
prompt: NewUserPrompt(ctx, AnchorBottom, 1+extraOffset),
prompt: NewUserPrompt(AnchorBottom, 1+extraOffset, state.Prompt(), state.Styles()),
// The list area is at the bottom, above the prompt
// It's displayed in bottom-to-top order
list: NewListArea(ctx, AnchorBottom, 2+extraOffset, false),
list: NewListArea(AnchorBottom, 2+extraOffset, false, state.Styles()),
}
}
@ -489,25 +481,25 @@ func (l *BasicLayout) PurgeDisplayCache() {
// CalculatePage calculates which page we're displaying
func (l *BasicLayout) CalculatePage(state *Peco, perPage int) error {
buf := state.CurrentLineBuffer()
currentPage := l.currentPage
currentPage.page = (l.currentLine / perPage) + 1
currentPage.offset = (currentPage.page - 1) * perPage
currentPage.perPage = perPage
currentPage.total = buf.Size()
loc := state.Location()
loc.SetPage((loc.LineNumber() / perPage) + 1)
loc.SetOffset((loc.Page() - 1) * perPage)
loc.SetPerPage(perPage)
loc.SetTotal(buf.Size())
trace("BasicLayout.CalculatePage: %#v", currentPage)
if currentPage.total == 0 {
currentPage.maxPage = 1
trace("BasicLayout.CalculatePage: %#v", loc)
if loc.Total() == 0 {
loc.SetMaxPage(1)
} else {
currentPage.maxPage = ((currentPage.total + perPage - 1) / perPage)
loc.SetMaxPage((loc.Total() + perPage - 1) / perPage)
}
if currentPage.maxPage < currentPage.page {
if loc.MaxPage() < loc.Page() {
if buf.Size() == 0 {
// wait for targets
return fmt.Errorf("no targets or query. nothing to do")
return errors.New("no targets or query. nothing to do")
}
l.currentLine = currentPage.offset
loc.SetLineNumber(loc.Offset())
}
return nil
@ -559,15 +551,16 @@ func (l *BasicLayout) MovePage(state *Peco, p PagingRequest) (moved bool) {
// verticalScroll moves the cursor position vertically
func verticalScroll(state *Peco, l *BasicLayout, p PagingRequest) bool {
// Before we move, on which line were we located?
lineBefore := l.currentLine
loc := state.Location()
lineBefore := loc.LineNumber()
lineno := lineBefore
defer func() { trace("currentLine changed from %d -> %d", lineBefore, l.currentLine) }()
cp := state.PageInfo()
defer func() { trace("currentLine changed from %d -> %d", lineBefore, state.Location().LineNumber()) }()
buf := state.CurrentLineBuffer()
lcur := buf.Size()
defer func() {
for _, lno := range []int{lineBefore, l.currentLine} {
for _, lno := range []int{lineBefore, loc.LineNumber()} {
if oldLine, err := buf.LineAt(lno); err == nil {
trace("Setting line %d dirty", lno)
oldLine.SetDirty(true)
@ -579,45 +572,48 @@ func verticalScroll(state *Peco, l *BasicLayout, p PagingRequest) bool {
if l.list.sortTopDown {
switch p.Type() {
case ToLineAbove:
l.currentLine--
lineno--
case ToLineBelow:
l.currentLine++
lineno++
case ToScrollPageDown:
l.currentLine += lpp
if cp.page == cp.maxPage-1 && lcur < l.currentLine && (lcur-lineBefore) < lpp {
l.currentLine = lcur - 1
lineno += lpp
if loc.Page() == loc.MaxPage()-1 && lcur < lineno && (lcur-lineBefore) < lpp {
lineno = lcur - 1
}
case ToScrollPageUp:
l.currentLine -= lpp
lineno -= lpp
case ToLineInPage:
l.currentLine = cp.perPage*(cp.page-1) + p.(JumpToLineRequest).Line()
lineno = loc.PerPage()*(loc.Page()-1) + p.(JumpToLineRequest).Line()
}
} else {
switch p.Type() {
case ToLineAbove:
l.currentLine++
lineno++
case ToLineBelow:
l.currentLine--
lineno--
case ToScrollPageDown:
l.currentLine -= lpp
lineno -= lpp
case ToScrollPageUp:
l.currentLine += lpp
lineno += lpp
case ToLineInPage:
l.currentLine = cp.perPage*(cp.page-1) - p.(JumpToLineRequest).Line()
lineno = loc.PerPage()*(loc.Page()-1) - p.(JumpToLineRequest).Line()
}
}
if l.currentLine < 0 {
if lineno < 0 {
if lcur > 0 {
// Go to last page, if possible
l.currentLine = lcur - 1
lineno = lcur - 1
} else {
l.currentLine = 0
lineno = 0
}
} else if lcur > 0 && l.currentLine >= lcur {
l.currentLine = 0
} else if lcur > 0 && lineno >= lcur {
lineno = 0
}
// XXX DO NOT RETURN UNTIL YOU SET THE LINE NUMBER HERE
loc.SetLineNumber(lineno)
// if we were in range mode, we need to do stuff. otherwise
// just bail out
if !state.RangeMode() {
@ -626,42 +622,42 @@ func verticalScroll(state *Peco, l *BasicLayout, p PagingRequest) bool {
sel := state.Selection()
if l.list.sortTopDown {
if l.currentLine < l.selectionRangeStart {
for lineno := l.currentLine; lineno <= l.selectionRangeStart; lineno++ {
if loc.LineNumber() < state.SelectionRangeStart() {
for lineno := loc.LineNumber(); lineno <= state.SelectionRangeStart(); lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Add(line)
}
}
switch {
case l.selectionRangeStart <= lineBefore:
for lineno := l.selectionRangeStart; lineno <= lcur && lineno < lineBefore; lineno++ {
case state.SelectionRangeStart() <= lineBefore:
for lineno := state.SelectionRangeStart(); lineno <= lcur && lineno < lineBefore; lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Remove(line)
}
}
case lineBefore < l.currentLine:
for lineno := lineBefore; lineno < l.currentLine; lineno++ {
case lineBefore < loc.LineNumber():
for lineno := lineBefore; lineno < loc.LineNumber(); lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Remove(line)
}
}
}
} else {
for lineno := l.selectionRangeStart; lineno <= lcur && lineno <= l.currentLine; lineno++ {
for lineno := state.SelectionRangeStart(); lineno <= lcur && lineno <= loc.LineNumber(); lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Add(line)
}
}
switch {
case lineBefore <= l.selectionRangeStart:
for lineno := lineBefore; lineno < l.selectionRangeStart; lineno++ {
case lineBefore <= state.SelectionRangeStart():
for lineno := lineBefore; lineno < state.SelectionRangeStart(); lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Remove(line)
}
}
case l.currentLine < lineBefore:
for lineno := l.currentLine; lineno <= lineBefore; lineno++ {
case loc.LineNumber() < lineBefore:
for lineno := loc.LineNumber(); lineno <= lineBefore; lineno++ {
if line, err := buf.LineAt(lineno); err == nil {
sel.Remove(line)
}
@ -676,13 +672,13 @@ func verticalScroll(state *Peco, l *BasicLayout, p PagingRequest) bool {
// horizontalScroll scrolls screen horizontal
func horizontalScroll(state *Peco, l *BasicLayout, p PagingRequest) bool {
width, _ := screen.Size()
loc := state.Location()
if p.Type() == ToScrollRight {
l.currentCol += width / 2
} else if l.currentCol > 0 {
l.currentCol -= width / 2
if l.currentCol < 0 {
l.currentCol = 0
loc.SetColumn(loc.Column() + width / 2)
} else if loc.Column() > 0 {
loc.SetColumn(loc.Column() - width / 2)
if loc.Column() < 0 {
loc.SetColumn(0)
}
} else {
return false

62
page.go
View file

@ -1,29 +1,65 @@
package peco
func (pi PageInfo) Offset() int {
return pi.offset
func (l *Location) SetColumn(n int) {
l.col = n
}
func (pi PageInfo) PerPage() int {
return pi.perPage
func (l Location) Column() int {
return l.col
}
func (pi PageInfo) Page() int {
return pi.page
func (l *Location) SetLineNumber(n int) {
l.lineno = n
}
func (pi PageInfo) Total() int {
return pi.total
func (l Location) LineNumber() int {
return l.lineno
}
func (pi PageInfo) MaxPage() int {
return pi.maxPage
func (l *Location) SetOffset(n int) {
l.offset = n
}
func (pi PageInfo) PageCrop() PageCrop {
func (l Location) Offset() int {
return l.offset
}
func (l *Location) SetPerPage(n int) {
l.perPage = n
}
func (l Location) PerPage() int {
return l.perPage
}
func (l *Location) SetPage(n int) {
l.page = n
}
func (l Location) Page() int {
return l.page
}
func (l *Location) SetTotal(n int) {
l.total = n
}
func (l Location) Total() int {
return l.total
}
func (l *Location) SetMaxPage(n int) {
l.maxPage = n
}
func (l Location) MaxPage() int {
return l.maxPage
}
func (l Location) PageCrop() PageCrop {
return PageCrop{
perPage: pi.perPage,
currentPage: pi.page,
perPage: l.perPage,
currentPage: l.page,
}
}

69
peco.go
View file

@ -49,23 +49,26 @@ type Peco struct {
args []string
caret Caret
// Config contains the values read in from config file
config Config
ctx context.Context
currentLine int
filters FilterSet
keymap Keymap
enableSep bool // Enable parsing on separators
inputseq Inputseq // current key sequence (just the names)
layoutType string
pageInfo PageInfo
query Query
queryExecDelay time.Duration
queryExecMutex sync.Mutex
queryExecTimer *time.Timer
resultCh chan Line
selection *Selection
selectionRangeStart int
singleKeyJumpMode bool
config Config
ctx context.Context
filters FilterSet
keymap Keymap
enableSep bool // Enable parsing on separators
inputseq Inputseq // current key sequence (just the names)
layoutType string
location Location
prompt string
query Query
queryExecDelay time.Duration
queryExecMutex sync.Mutex
queryExecTimer *time.Timer
resultCh chan Line
selection *Selection
selectionRangeStart int
singleKeyJumpMode bool
singleKeyJumpPrefixes []rune
singleKeyJumpShowPrefix bool
styles StyleSet
Options CLIOptions
@ -81,11 +84,20 @@ type Peco struct {
func New() *Peco {
return &Peco{
activeLineBuffer: &MemoryBuffer{}, // XXX revisit this
selection: NewSelection(),
selectionRangeStart: invalidSelectionRange,
}
}
func (p Peco) Styles() *StyleSet {
return &p.styles
}
func (p Peco) Prompt() string {
return p.prompt
}
func (p Peco) Inputseq() *Inputseq {
return &p.inputseq
}
@ -94,14 +106,6 @@ func (p Peco) Context() context.Context {
return p.ctx
}
func (p *Peco) SetCurrentLine(n int) {
p.currentLine = n
}
func (p Peco) CurrentLine() int {
return p.currentLine
}
func (p Peco) CurrentLineBuffer() LineBuffer {
return nil // XXX DUMMY
}
@ -110,8 +114,8 @@ func (p Peco) LayoutType() string {
return p.layoutType
}
func (p *Peco) PageInfo() *PageInfo {
return &p.pageInfo
func (p *Peco) Location() *Location {
return &p.location
}
func (p Peco) ResultCh() chan Line {
@ -140,6 +144,14 @@ func (p Peco) RangeMode() bool {
return p.selectionRangeStart != invalidSelectionRange
}
func (p Peco) SingleKeyJumpShowPrefix() bool {
return p.singleKeyJumpShowPrefix
}
func (p Peco) SingleKeyJumpPrefixes() []rune {
return p.singleKeyJumpPrefixes
}
func (p Peco) SingleKeyJumpMode() bool {
return p.singleKeyJumpMode
}
@ -258,6 +270,7 @@ func (p *Peco) Run() error {
go l.Loop(ctx, cancel)
}
trace("peco is now ready, go go go!")
<-ctx.Done()
return p.Err()
@ -291,7 +304,7 @@ func (p *Peco) SetupSource() (*Source, error) {
src := NewSource(in, p.enableSep)
// Block until we receive something from `in`
go src.Setup()
go src.Setup(p)
<-src.Ready()
return src, nil

View file

@ -58,7 +58,7 @@ func TestIDGen(t *testing.T) {
}
func TestPeco(t *testing.T) {
p := Peco{}
p := New()
p.Argv = []string{"peco", "peco_test.go"}
time.AfterFunc(time.Second, func() {

View file

@ -39,7 +39,7 @@ func TestSource(t *testing.T) {
r := addReadDelay(strings.NewReader(strings.Join(lines, "\n")), 2*time.Second)
s := NewSource(r, false)
go s.Setup()
go s.Setup(nil)
timeout := time.After(5 * time.Second)
waitout := time.After(1 * time.Second)

View file

@ -113,8 +113,17 @@ func (v *View) movePage(p PagingRequest) {
*/
func NewView(state *Peco) *View {
var layout Layout
switch state.LayoutType() {
case LayoutTypeBottomUp:
layout = NewBottomUpLayout(state)
default:
layout = NewDefaultLayout(state)
}
return &View{
state: state,
layout: layout,
}
}