From 417be36cfb9e0e4bff3b91aed7fa32a08344debf Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 26 May 2016 21:56:13 +0900 Subject: [PATCH] More refactoring around current position/location state --- action.go | 12 +-- buffer.go | 37 +++++++- interface.go | 38 +++----- layout.go | 244 ++++++++++++++++++++++++------------------------- page.go | 62 ++++++++++--- peco.go | 69 ++++++++------ peco_test.go | 2 +- source_test.go | 2 +- view.go | 9 ++ 9 files changed, 275 insertions(+), 200 deletions(-) diff --git a/action.go b/action.go index 6d9b722..2afd5ae 100644 --- a/action.go +++ b/action.go @@ -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) } } diff --git a/buffer.go b/buffer.go index b8260bf..baeb5b8 100644 --- a/buffer.go +++ b/buffer.go @@ -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{}{} } }) } diff --git a/interface.go b/interface.go index cfd2616..34eb91d 100644 --- a/interface.go +++ b/interface.go @@ -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 diff --git a/layout.go b/layout.go index 76e5c99..87e01cf 100644 --- a/layout.go +++ b/layout.go @@ -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 diff --git a/page.go b/page.go index e26737d..50e603b 100644 --- a/page.go +++ b/page.go @@ -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, } } diff --git a/peco.go b/peco.go index c3a0112..1904277 100644 --- a/peco.go +++ b/peco.go @@ -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 diff --git a/peco_test.go b/peco_test.go index 9425440..81e16f4 100644 --- a/peco_test.go +++ b/peco_test.go @@ -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() { diff --git a/source_test.go b/source_test.go index cfa2b44..4f0418c 100644 --- a/source_test.go +++ b/source_test.go @@ -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) diff --git a/view.go b/view.go index b746efd..f969f3d 100644 --- a/view.go +++ b/view.go @@ -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, } }