From 068c98286474f0ec40194ee7ca3952e8ed0715f2 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 07:33:31 +0900 Subject: [PATCH 01/19] Rip screen drawing out of View View handles communication and control of elements in the Layout. Layout does the actual drawing. --- config.go | 50 +++++++++- ctx.go | 4 +- layout.go | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ view.go | 228 ++++--------------------------------------- 4 files changed, 352 insertions(+), 217 deletions(-) create mode 100644 layout.go diff --git a/config.go b/config.go index 51875a7..07ee830 100644 --- a/config.go +++ b/config.go @@ -22,7 +22,7 @@ type Config struct { Keymap map[string]string `json:"Keymap"` Matcher string `json:"Matcher"` // Deprecated. InitialMatcher string `json:"InitialMatcher"` // Use this instead of Matcher - Style StyleSet `json:"Style"` + Style *StyleSet `json:"Style"` Prompt string `json:"Prompt"` CustomMatcher map[string][]string } @@ -97,16 +97,56 @@ type StyleSet struct { } // NewStyleSet creates a new StyleSet struct -func NewStyleSet() StyleSet { - return StyleSet{ +func NewStyleSet() *StyleSet { + return &StyleSet{ Basic: Style{fg: termbox.ColorDefault, bg: termbox.ColorDefault}, - SavedSelection: Style{fg: termbox.ColorBlack | termbox.AttrBold, bg: termbox.ColorCyan}, - Selected: Style{fg: termbox.ColorDefault | termbox.AttrUnderline, bg: termbox.ColorMagenta}, Query: Style{fg: termbox.ColorDefault, bg: termbox.ColorDefault}, Matched: Style{fg: termbox.ColorCyan, bg: termbox.ColorDefault}, + SavedSelection: Style{fg: termbox.ColorBlack | termbox.AttrBold, bg: termbox.ColorCyan}, + Selected: Style{fg: termbox.ColorDefault | termbox.AttrUnderline, bg: termbox.ColorMagenta}, } } +func (s StyleSet) BasicFG() termbox.Attribute { + return s.Basic.fg +} + +func (s StyleSet) BasicBG() termbox.Attribute { + return s.Basic.bg +} + +func (s StyleSet) QueryFG() termbox.Attribute { + return s.Query.fg +} + +func (s StyleSet) QueryBG() termbox.Attribute { + return s.Query.bg +} + +func (s StyleSet) MatchedFG() termbox.Attribute { + return s.Matched.fg +} + +func (s StyleSet) MatchedBG() termbox.Attribute { + return s.Matched.bg +} + +func (s StyleSet) SavedSelectionFG() termbox.Attribute { + return s.SavedSelection.fg +} + +func (s StyleSet) SavedSelectionBG() termbox.Attribute { + return s.SavedSelection.bg +} + +func (s StyleSet) SelectedFG() termbox.Attribute { + return s.Selected.fg +} + +func (s StyleSet) SelectedBG() termbox.Attribute { + return s.Selected.bg +} + // Style describes termbox styles type Style struct { fg termbox.Attribute diff --git a/ctx.go b/ctx.go index 0bd9098..7366746 100644 --- a/ctx.go +++ b/ctx.go @@ -44,6 +44,7 @@ type Ctx struct { caretPos int currentLine int currentPage PageInfo + maxPage int selection Selection lines []Match current []Match @@ -68,6 +69,7 @@ func NewCtx(o CtxOptions) *Ctx { 0, o.InitialIndex(), struct{ index, offset, perPage int }{0, 1, 0}, + 0, Selection([]int{}), []Match{}, nil, @@ -179,7 +181,7 @@ func (c *Ctx) NewBufferReader(r io.ReadCloser) *BufferReader { } func (c *Ctx) NewView() *View { - return &View{c, nil} + return &View{c, NewDefaultLayout(c)} } func (c *Ctx) NewFilter() *Filter { diff --git a/layout.go b/layout.go new file mode 100644 index 0000000..83c5c61 --- /dev/null +++ b/layout.go @@ -0,0 +1,287 @@ +package peco + +import ( + "fmt" + "time" + "unicode/utf8" + + "github.com/mattn/go-runewidth" + "github.com/nsf/termbox-go" +) + +type Layout interface { + ClearStatus(time.Duration) + PrintStatus(string) + DrawScreen([]Match) +} + +// Utility function +func mergeAttribute(a, b termbox.Attribute) termbox.Attribute { + if a&0x0F == 0 || b&0x0F == 0 { + return a | b + } else { + return ((a - 1) | (b - 1)) + 1 + } +} + +// Utility function +func printScreen(x, y int, fg, bg termbox.Attribute, msg string, fill bool) { + for len(msg) > 0 { + c, w := utf8.DecodeRuneInString(msg) + if c == utf8.RuneError { + c = '?' + w = 1 + } + msg = msg[w:] + termbox.SetCell(x, y, c, fg, bg) + x += runewidth.RuneWidth(c) + } + + if !fill { + return + } + + width, _ := termbox.Size() + for ; x < width; x++ { + termbox.SetCell(x, y, ' ', fg, bg) + } +} + +// UserPrompt draws the prompt line +type UserPrompt struct { + *Ctx + prefix string + prefixLen int +} + +func NewUserPrompt(ctx *Ctx) *UserPrompt { + prefix := ctx.config.Prompt + if len(prefix) <= 0 { // default + prefix = "QUERY>" + } + prefixLen := runewidth.StringWidth(prefix) + + return &UserPrompt{ + Ctx: ctx, + prefix: prefix, + prefixLen: prefixLen, + } +} + +func (u UserPrompt) Draw() { + // print "QUERY>" + printScreen(0, 0, u.config.Style.BasicFG(), u.config.Style.BasicBG(), u.prefix, false) + + if u.caretPos <= 0 { + u.caretPos = 0 // sanity + } + + if u.caretPos > len(u.query) { + u.caretPos = len(u.query) + } + + if u.caretPos == len(u.query) { + // the entire string + the caret after the string + fg := u.config.Style.QueryFG() + bg := u.config.Style.QueryBG() + qs := string(u.query) + ql := runewidth.StringWidth(qs) + printScreen(u.prefixLen+1, 0, fg, bg, qs, false) + printScreen(u.prefixLen+1+ql, 0, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false) + printScreen(u.prefixLen+1+ql+1, 0, fg, bg, "", true) + } else { + // the caret is in the middle of the string + prev := 0 + fg := u.config.Style.QueryFG() + bg := u.config.Style.QueryBG() + for i, r := range u.query { + if i == u.caretPos { + fg |= termbox.AttrReverse + bg |= termbox.AttrReverse + } + termbox.SetCell(u.prefixLen+1+prev, 0, r, fg, bg) + prev += runewidth.RuneWidth(r) + } + } + + width, _ := termbox.Size() + + pmsg := fmt.Sprintf("%s [%d/%d]", u.Matcher().String(), u.currentPage.index, u.maxPage) + printScreen(width-runewidth.StringWidth(pmsg), 0, u.config.Style.BasicFG(), u.config.Style.BasicBG(), pmsg, false) +} + +// StatusBar draws the status message bar +type StatusBar struct { + *Ctx + clearTimer *time.Timer +} + +func NewStatusBar(ctx *Ctx) *StatusBar { + return &StatusBar{ + ctx, + nil, + } +} + +func (s *StatusBar) stopTimer() { + if t := s.clearTimer; t != nil { + t.Stop() + } +} + +func (s *StatusBar) ClearStatus(d time.Duration) { + s.stopTimer() + s.clearTimer = time.AfterFunc(d, func() { + s.PrintStatus("") + }) +} + +func (s *StatusBar) PrintStatus(msg string) { + s.stopTimer() + + w, h := termbox.Size() + + width := runewidth.StringWidth(msg) + for width > w { + _, rw := utf8.DecodeRuneInString(msg) + width = width - rw + msg = msg[rw:] + } + + var pad []byte + if w > width { + pad = make([]byte, w-width) + for i := 0; i < w-width; i++ { + pad[i] = ' ' + } + } + + fgAttr := s.config.Style.BasicFG() + bgAttr := s.config.Style.BasicBG() + + if w > width { + printScreen(0, h-2, fgAttr, bgAttr, string(pad), false) + } + + if width > 0 { + printScreen(w-width, h-2, fgAttr|termbox.AttrReverse|termbox.AttrBold, bgAttr|termbox.AttrReverse, msg, false) + } + termbox.Flush() +} + +type basicLayout struct { + *Ctx + *StatusBar + *UserPrompt +} + +// DefaultLayout implements the top-down layout +type DefaultLayout struct { + *basicLayout +} +type BottomUpLayout struct { + *basicLayout +} + +func NewDefaultLayout(ctx *Ctx) *DefaultLayout { + return &DefaultLayout{ + &basicLayout{ + Ctx: ctx, + StatusBar: NewStatusBar(ctx), + UserPrompt: NewUserPrompt(ctx), + }, + } +} + +func (l *DefaultLayout) DrawScreen(targets []Match) { + fgAttr := l.config.Style.BasicFG() + bgAttr := l.config.Style.BasicBG() + + if err := termbox.Clear(fgAttr, bgAttr); err != nil { + return + } + + if l.currentLine > len(targets) && len(targets) > 0 { + l.currentLine = len(targets) + } + + _, height := termbox.Size() + perPage := height - 4 + +CALCULATE_PAGE: + currentPage := l.currentPage + currentPage.index = ((l.currentLine - 1) / perPage) + 1 + if currentPage.index <= 0 { + currentPage.index = 1 + } + currentPage.offset = (currentPage.index - 1) * perPage + currentPage.perPage = perPage + if len(targets) == 0 { + l.maxPage = 1 + } else { + l.maxPage = ((len(targets) + perPage - 1) / perPage) + } + + if l.maxPage < currentPage.index { + if len(targets) == 0 && len(l.query) == 0 { + // wait for targets + return + } + l.currentLine = currentPage.offset + goto CALCULATE_PAGE + } + + l.UserPrompt.Draw() + + for n := 1; n <= perPage; n++ { + switch { + case n+currentPage.offset == l.currentLine: + fgAttr = l.config.Style.SelectedFG() + bgAttr = l.config.Style.SelectedBG() + case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset): + fgAttr = l.config.Style.SavedSelectionFG() + bgAttr = l.config.Style.SavedSelectionBG() + default: + fgAttr = l.config.Style.BasicFG() + bgAttr = l.config.Style.BasicBG() + } + + targetIdx := currentPage.offset + n - 1 + if targetIdx >= len(targets) { + break + } + + target := targets[targetIdx] + line := target.Line() + matches := target.Indices() + if matches == nil { + printScreen(0, n, fgAttr, bgAttr, line, true) + } else { + prev := 0 + index := 0 + for _, m := range matches { + if m[0] > index { + c := line[index:m[0]] + printScreen(prev, n, fgAttr, bgAttr, c, false) + prev += runewidth.StringWidth(c) + index += len(c) + } + c := line[m[0]:m[1]] + printScreen(prev, n, l.config.Style.MatchedFG(), mergeAttribute(bgAttr, l.config.Style.MatchedBG()), c, true) + prev += runewidth.StringWidth(c) + index += len(c) + } + + m := matches[len(matches)-1] + if m[0] > index { + printScreen(prev, n, l.config.Style.QueryFG(), mergeAttribute(bgAttr, l.config.Style.QueryBG()), line[m[0]:m[1]], true) + } else if len(line) > m[1] { + printScreen(prev, n, fgAttr, bgAttr, line[m[1]:len(line)], true) + } + } + } + + if err := termbox.Flush(); err != nil { + return + } +} diff --git a/view.go b/view.go index d092af8..e3e7427 100644 --- a/view.go +++ b/view.go @@ -1,18 +1,15 @@ package peco import ( - "fmt" "time" - "unicode/utf8" - "github.com/mattn/go-runewidth" "github.com/nsf/termbox-go" ) // View handles the drawing/updating the screen type View struct { *Ctx - clearTimer *time.Timer + layout Layout } // PagingRequest can be sent to move the selection cursor @@ -52,67 +49,29 @@ func (v *View) Loop() { } } +func (v *View) printStatus(m string) { + v.layout.PrintStatus(m) +} + func (v *View) clearStatus(d time.Duration) { - if t := v.clearTimer; t != nil { - t.Stop() - } - - v.clearTimer = time.AfterFunc(d, func() { - v.printStatus("") - }) + v.layout.ClearStatus(d) } -func (v *View) printStatus(msg string) { - if t := v.clearTimer; t != nil { - t.Stop() - } +func (v *View) drawScreen(targets []Match) { + v.mutex.Lock() + defer v.mutex.Unlock() - w, h := termbox.Size() - - width := runewidth.StringWidth(msg) - for width > w { - _, rw := utf8.DecodeRuneInString(msg) - width = width - rw - msg = msg[rw:] - } - - var pad []byte - if w > width { - pad = make([]byte, w-width) - for i := 0; i < w-width; i++ { - pad[i] = ' ' + if targets == nil { + if current := v.current; current != nil { + targets = v.current + } else { + targets = v.lines } } - fgAttr := v.config.Style.Basic.fg - bgAttr := v.config.Style.Basic.bg - - if w > width { - printTB(0, h-2, fgAttr, bgAttr, string(pad)) - } - - if width > 0 { - printTB(w-width, h-2, fgAttr|termbox.AttrReverse|termbox.AttrBold, bgAttr|termbox.AttrReverse, msg) - } - termbox.Flush() -} - -func printTB(x, y int, fg, bg termbox.Attribute, msg string) { - for len(msg) > 0 { - c, w := utf8.DecodeRuneInString(msg) - if c == utf8.RuneError { - c = '?' - w = 1 - } - msg = msg[w:] - termbox.SetCell(x, y, c, fg, bg) - x += runewidth.RuneWidth(c) - } - - width, _ := termbox.Size() - for ; x < width; x++ { - termbox.SetCell(x, y, ' ', fg, bg) - } + v.layout.DrawScreen(targets) + // FIXME + v.current = targets } func (v *View) movePage(p PagingRequest) { @@ -144,156 +103,3 @@ func (v *View) movePage(p PagingRequest) { } v.drawScreen(nil) } - -func (v *View) drawScreen(targets []Match) { - v.mutex.Lock() - defer v.mutex.Unlock() - - fgAttr := v.config.Style.Basic.fg - bgAttr := v.config.Style.Basic.bg - - if err := termbox.Clear(fgAttr, bgAttr); err != nil { - return - } - - if targets == nil { - if current := v.Ctx.current; current != nil { - targets = v.Ctx.current - } else { - targets = v.Ctx.lines - } - } - if v.Ctx.currentLine > len(targets) && len(targets) > 0 { - v.Ctx.currentLine = len(targets) - } - - width, height := termbox.Size() - perPage := height - 4 - -CALCULATE_PAGE: - currentPage := &v.Ctx.currentPage - currentPage.index = ((v.Ctx.currentLine - 1) / perPage) + 1 - if currentPage.index <= 0 { - currentPage.index = 1 - } - currentPage.offset = (currentPage.index - 1) * perPage - currentPage.perPage = perPage - var maxPage int - if len(targets) == 0 { - maxPage = 1 - } else { - maxPage = ((len(targets) + perPage - 1) / perPage) - } - - if maxPage < currentPage.index { - if len(targets) == 0 && len(v.Ctx.query) == 0 { - // wait for targets - return - } - v.Ctx.currentLine = currentPage.offset - goto CALCULATE_PAGE - } - - fgAttr = v.config.Style.Query.fg - bgAttr = v.config.Style.Query.bg - - var prompt string - if len(v.Ctx.prompt) > 0 { - prompt = string(v.Ctx.prompt) - } else { - prompt = v.config.Prompt - } - promptLen := runewidth.StringWidth(prompt) - printTB(0, 0, fgAttr, bgAttr, prompt) - - if v.caretPos <= 0 { - v.caretPos = 0 // sanity - } - if v.caretPos > len(v.query) { - v.caretPos = len(v.query) - } - - if v.caretPos == len(v.query) { - // the entire string + the caret after the string - printTB(promptLen+1, 0, fgAttr, bgAttr, string(v.query)) - termbox.SetCell(promptLen+1+runewidth.StringWidth(string(v.query)), 0, ' ', fgAttr|termbox.AttrReverse, bgAttr|termbox.AttrReverse) - } else { - // the caret is in the middle of the string - prev := 0 - for i, r := range v.query { - fg := v.config.Style.Query.fg - bg := v.config.Style.Query.bg - if i == v.caretPos { - fg |= termbox.AttrReverse - bg |= termbox.AttrReverse - } - termbox.SetCell(promptLen+1+prev, 0, r, fg, bg) - prev += runewidth.RuneWidth(r) - } - } - - pmsg := fmt.Sprintf("%s [%d/%d]", v.Ctx.Matcher().String(), currentPage.index, maxPage) - - printTB(width-runewidth.StringWidth(pmsg), 0, fgAttr, bgAttr, pmsg) - - for n := 1; n <= perPage; n++ { - fgAttr = v.config.Style.Basic.fg - bgAttr = v.config.Style.Basic.bg - if n+currentPage.offset == v.currentLine { - fgAttr = v.config.Style.Selected.fg - bgAttr = v.config.Style.Selected.bg - } else if v.selection.Has(n+currentPage.offset) || v.SelectedRange().Has(n+currentPage.offset) { - fgAttr = v.config.Style.SavedSelection.fg - bgAttr = v.config.Style.SavedSelection.bg - } - - targetIdx := currentPage.offset + n - 1 - if targetIdx >= len(targets) { - break - } - - target := targets[targetIdx] - line := target.Line() - matches := target.Indices() - if matches == nil { - printTB(0, n, fgAttr, bgAttr, line) - } else { - prev := 0 - index := 0 - for _, m := range matches { - if m[0] > index { - c := line[index:m[0]] - printTB(prev, n, fgAttr, bgAttr, c) - prev += runewidth.StringWidth(c) - index += len(c) - } - c := line[m[0]:m[1]] - printTB(prev, n, v.config.Style.Matched.fg, mergeAttribute(bgAttr, v.config.Style.Matched.bg), c) - prev += runewidth.StringWidth(c) - index += len(c) - } - - m := matches[len(matches)-1] - if m[0] > index { - printTB(prev, n, v.config.Style.Query.fg, mergeAttribute(bgAttr, v.config.Style.Query.bg), line[m[0]:m[1]]) - } else if len(line) > m[1] { - printTB(prev, n, fgAttr, bgAttr, line[m[1]:len(line)]) - } - } - } - - if err := termbox.Flush(); err != nil { - return - } - - // FIXME - v.current = targets -} - -func mergeAttribute(a, b termbox.Attribute) termbox.Attribute { - if a&0x0F == 0 || b&0x0F == 0 { - return a | b - } else { - return ((a - 1) | (b - 1)) + 1 - } -} From 89beeeb3badd83214accf8c7205714a3f854b9c4 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 15:49:54 +0900 Subject: [PATCH 02/19] Fix a long-standing regression for PageInfo Because we were this struct as a value, the value inside Ctx never got updated :/ --- ctx.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ctx.go b/ctx.go index 7366746..35e362d 100644 --- a/ctx.go +++ b/ctx.go @@ -43,7 +43,7 @@ type Ctx struct { prompt []rune caretPos int currentLine int - currentPage PageInfo + currentPage *PageInfo maxPage int selection Selection lines []Match @@ -68,7 +68,7 @@ func NewCtx(o CtxOptions) *Ctx { []rune{}, 0, o.InitialIndex(), - struct{ index, offset, perPage int }{0, 1, 0}, + &PageInfo{0, 1, 0}, 0, Selection([]int{}), []Match{}, From 2b8a2aab5a28d00b8b1b52eb25cc7d62d59861cc Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 15:51:23 +0900 Subject: [PATCH 03/19] rip out page calculation --- layout.go | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/layout.go b/layout.go index 83c5c61..13dce4b 100644 --- a/layout.go +++ b/layout.go @@ -193,21 +193,7 @@ func NewDefaultLayout(ctx *Ctx) *DefaultLayout { } } -func (l *DefaultLayout) DrawScreen(targets []Match) { - fgAttr := l.config.Style.BasicFG() - bgAttr := l.config.Style.BasicBG() - - if err := termbox.Clear(fgAttr, bgAttr); err != nil { - return - } - - if l.currentLine > len(targets) && len(targets) > 0 { - l.currentLine = len(targets) - } - - _, height := termbox.Size() - perPage := height - 4 - +func (l *DefaultLayout) CalculatePage(targets []Match, perPage int) error { CALCULATE_PAGE: currentPage := l.currentPage currentPage.index = ((l.currentLine - 1) / perPage) + 1 @@ -225,13 +211,36 @@ CALCULATE_PAGE: if l.maxPage < currentPage.index { if len(targets) == 0 && len(l.query) == 0 { // wait for targets - return + return fmt.Errorf("no targets or query. nothing to do") } l.currentLine = currentPage.offset goto CALCULATE_PAGE } + return nil +} + +func (l *DefaultLayout) DrawScreen(targets []Match) { + fgAttr := l.config.Style.BasicFG() + bgAttr := l.config.Style.BasicBG() + + if err := termbox.Clear(fgAttr, bgAttr); err != nil { + return + } + + if l.currentLine > len(targets) && len(targets) > 0 { + l.currentLine = len(targets) + } + + _, height := termbox.Size() + perPage := height - 4 + + if err := l.CalculatePage(targets, perPage); err != nil { + return + } + l.UserPrompt.Draw() + currentPage := l.currentPage for n := 1; n <= perPage; n++ { switch { From 006228e0a538c7ec5d8534e24e71a2c6a4d714b6 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 16:33:42 +0900 Subject: [PATCH 04/19] Rip out list area from layout --- layout.go | 138 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 59 deletions(-) diff --git a/layout.go b/layout.go index 13dce4b..e75f509 100644 --- a/layout.go +++ b/layout.go @@ -50,6 +50,7 @@ func printScreen(x, y int, fg, bg termbox.Attribute, msg string, fill bool) { // UserPrompt draws the prompt line type UserPrompt struct { *Ctx + location int prefix string prefixLen int } @@ -63,6 +64,7 @@ func NewUserPrompt(ctx *Ctx) *UserPrompt { return &UserPrompt{ Ctx: ctx, + location: 0, // effectively, the line number where the prompt is going to be displayed at prefix: prefix, prefixLen: prefixLen, } @@ -70,7 +72,7 @@ func NewUserPrompt(ctx *Ctx) *UserPrompt { func (u UserPrompt) Draw() { // print "QUERY>" - printScreen(0, 0, u.config.Style.BasicFG(), u.config.Style.BasicBG(), u.prefix, false) + printScreen(0, u.location, u.config.Style.BasicFG(), u.config.Style.BasicBG(), u.prefix, false) if u.caretPos <= 0 { u.caretPos = 0 // sanity @@ -86,9 +88,9 @@ func (u UserPrompt) Draw() { bg := u.config.Style.QueryBG() qs := string(u.query) ql := runewidth.StringWidth(qs) - printScreen(u.prefixLen+1, 0, fg, bg, qs, false) - printScreen(u.prefixLen+1+ql, 0, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false) - printScreen(u.prefixLen+1+ql+1, 0, fg, bg, "", true) + printScreen(u.prefixLen+1, u.location, fg, bg, qs, false) + printScreen(u.prefixLen+1+ql, u.location, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false) + printScreen(u.prefixLen+1+ql+1, u.location, fg, bg, "", true) } else { // the caret is in the middle of the string prev := 0 @@ -99,7 +101,7 @@ func (u UserPrompt) Draw() { fg |= termbox.AttrReverse bg |= termbox.AttrReverse } - termbox.SetCell(u.prefixLen+1+prev, 0, r, fg, bg) + termbox.SetCell(u.prefixLen+1+prev, u.location, r, fg, bg) prev += runewidth.RuneWidth(r) } } @@ -107,7 +109,7 @@ func (u UserPrompt) Draw() { width, _ := termbox.Size() pmsg := fmt.Sprintf("%s [%d/%d]", u.Matcher().String(), u.currentPage.index, u.maxPage) - printScreen(width-runewidth.StringWidth(pmsg), 0, u.config.Style.BasicFG(), u.config.Style.BasicBG(), pmsg, false) + printScreen(width-runewidth.StringWidth(pmsg), u.location, u.config.Style.BasicFG(), u.config.Style.BasicBG(), pmsg, false) } // StatusBar draws the status message bar @@ -169,10 +171,76 @@ func (s *StatusBar) PrintStatus(msg string) { termbox.Flush() } +type ListArea struct { + *Ctx + sortTopDown bool +} + +func NewListArea(ctx *Ctx) *ListArea { + return &ListArea{ + ctx, + true, + } +} + +func (l *ListArea) Draw(targets []Match, perPage int) { + currentPage := l.currentPage + + var fgAttr, bgAttr termbox.Attribute + for n := 1; n <= perPage; n++ { + switch { + case n+currentPage.offset == l.currentLine: + fgAttr = l.config.Style.SelectedFG() + bgAttr = l.config.Style.SelectedBG() + case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset): + fgAttr = l.config.Style.SavedSelectionFG() + bgAttr = l.config.Style.SavedSelectionBG() + default: + fgAttr = l.config.Style.BasicFG() + bgAttr = l.config.Style.BasicBG() + } + + targetIdx := currentPage.offset + n - 1 + if targetIdx >= len(targets) { + break + } + + target := targets[targetIdx] + line := target.Line() + matches := target.Indices() + if matches == nil { + printScreen(0, n, fgAttr, bgAttr, line, true) + } else { + prev := 0 + index := 0 + for _, m := range matches { + if m[0] > index { + c := line[index:m[0]] + printScreen(prev, n, fgAttr, bgAttr, c, false) + prev += runewidth.StringWidth(c) + index += len(c) + } + c := line[m[0]:m[1]] + printScreen(prev, n, l.config.Style.MatchedFG(), mergeAttribute(bgAttr, l.config.Style.MatchedBG()), c, true) + prev += runewidth.StringWidth(c) + index += len(c) + } + + m := matches[len(matches)-1] + if m[0] > index { + printScreen(prev, n, l.config.Style.QueryFG(), mergeAttribute(bgAttr, l.config.Style.QueryBG()), line[m[0]:m[1]], true) + } else if len(line) > m[1] { + printScreen(prev, n, fgAttr, bgAttr, line[m[1]:len(line)], true) + } + } + } +} + type basicLayout struct { *Ctx *StatusBar - *UserPrompt + prompt *UserPrompt + list *ListArea } // DefaultLayout implements the top-down layout @@ -188,7 +256,8 @@ func NewDefaultLayout(ctx *Ctx) *DefaultLayout { &basicLayout{ Ctx: ctx, StatusBar: NewStatusBar(ctx), - UserPrompt: NewUserPrompt(ctx), + prompt: NewUserPrompt(ctx), + list: NewListArea(ctx), }, } } @@ -239,57 +308,8 @@ func (l *DefaultLayout) DrawScreen(targets []Match) { return } - l.UserPrompt.Draw() - currentPage := l.currentPage - - for n := 1; n <= perPage; n++ { - switch { - case n+currentPage.offset == l.currentLine: - fgAttr = l.config.Style.SelectedFG() - bgAttr = l.config.Style.SelectedBG() - case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset): - fgAttr = l.config.Style.SavedSelectionFG() - bgAttr = l.config.Style.SavedSelectionBG() - default: - fgAttr = l.config.Style.BasicFG() - bgAttr = l.config.Style.BasicBG() - } - - targetIdx := currentPage.offset + n - 1 - if targetIdx >= len(targets) { - break - } - - target := targets[targetIdx] - line := target.Line() - matches := target.Indices() - if matches == nil { - printScreen(0, n, fgAttr, bgAttr, line, true) - } else { - prev := 0 - index := 0 - for _, m := range matches { - if m[0] > index { - c := line[index:m[0]] - printScreen(prev, n, fgAttr, bgAttr, c, false) - prev += runewidth.StringWidth(c) - index += len(c) - } - c := line[m[0]:m[1]] - printScreen(prev, n, l.config.Style.MatchedFG(), mergeAttribute(bgAttr, l.config.Style.MatchedBG()), c, true) - prev += runewidth.StringWidth(c) - index += len(c) - } - - m := matches[len(matches)-1] - if m[0] > index { - printScreen(prev, n, l.config.Style.QueryFG(), mergeAttribute(bgAttr, l.config.Style.QueryBG()), line[m[0]:m[1]], true) - } else if len(line) > m[1] { - printScreen(prev, n, fgAttr, bgAttr, line[m[1]:len(line)], true) - } - } - } - + l.prompt.Draw() + l.list.Draw(targets, perPage) if err := termbox.Flush(); err != nil { return } From ca0d6f32a5a19c0bdb24c29f0e4d7446a0446d6f Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 16:46:06 +0900 Subject: [PATCH 05/19] Make it possible to dynamically calculate y-offset for ListArea --- layout.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/layout.go b/layout.go index e75f509..adb1f67 100644 --- a/layout.go +++ b/layout.go @@ -174,22 +174,33 @@ func (s *StatusBar) PrintStatus(msg string) { type ListArea struct { *Ctx sortTopDown bool + start int } func NewListArea(ctx *Ctx) *ListArea { return &ListArea{ ctx, true, + 1, } } +// given the n-th element to display, calculate which y offset that line should +// be displayed at +func (l *ListArea) calcYLocation(n int) int { + if l.sortTopDown { + return n + l.start + } + return l.start - n +} + func (l *ListArea) Draw(targets []Match, perPage int) { currentPage := l.currentPage var fgAttr, bgAttr termbox.Attribute - for n := 1; n <= perPage; n++ { + for n := 0; n < perPage; n++ { switch { - case n+currentPage.offset == l.currentLine: + case n+currentPage.offset == l.currentLine - l.start: fgAttr = l.config.Style.SelectedFG() bgAttr = l.config.Style.SelectedBG() case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset): @@ -200,37 +211,39 @@ func (l *ListArea) Draw(targets []Match, perPage int) { bgAttr = l.config.Style.BasicBG() } - targetIdx := currentPage.offset + n - 1 + targetIdx := currentPage.offset + n if targetIdx >= len(targets) { break } + y := l.calcYLocation(n) + target := targets[targetIdx] line := target.Line() matches := target.Indices() if matches == nil { - printScreen(0, n, fgAttr, bgAttr, line, true) + printScreen(0, y, fgAttr, bgAttr, line, true) } else { prev := 0 index := 0 for _, m := range matches { if m[0] > index { c := line[index:m[0]] - printScreen(prev, n, fgAttr, bgAttr, c, false) + printScreen(prev, y, fgAttr, bgAttr, c, false) prev += runewidth.StringWidth(c) index += len(c) } c := line[m[0]:m[1]] - printScreen(prev, n, l.config.Style.MatchedFG(), mergeAttribute(bgAttr, l.config.Style.MatchedBG()), c, true) + printScreen(prev, y, l.config.Style.MatchedFG(), mergeAttribute(bgAttr, l.config.Style.MatchedBG()), c, true) prev += runewidth.StringWidth(c) index += len(c) } m := matches[len(matches)-1] if m[0] > index { - printScreen(prev, n, l.config.Style.QueryFG(), mergeAttribute(bgAttr, l.config.Style.QueryBG()), line[m[0]:m[1]], true) + printScreen(prev, y, l.config.Style.QueryFG(), mergeAttribute(bgAttr, l.config.Style.QueryBG()), line[m[0]:m[1]], true) } else if len(line) > m[1] { - printScreen(prev, n, fgAttr, bgAttr, line[m[1]:len(line)], true) + printScreen(prev, y, fgAttr, bgAttr, line[m[1]:len(line)], true) } } } From ebdbbf937a9d084fac2afda2226b7dc4c1141c94 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 17:00:49 +0900 Subject: [PATCH 06/19] Make UserPrompt calculate its veritical position dynamically --- layout.go | 64 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/layout.go b/layout.go index adb1f67..4a2313f 100644 --- a/layout.go +++ b/layout.go @@ -9,6 +9,13 @@ import ( "github.com/nsf/termbox-go" ) +type VerticalAnchor int + +const ( + AnchorTop VerticalAnchor = iota + 1 + AnchorBottom +) + type Layout interface { ClearStatus(time.Duration) PrintStatus(string) @@ -50,12 +57,13 @@ func printScreen(x, y int, fg, bg termbox.Attribute, msg string, fill bool) { // UserPrompt draws the prompt line type UserPrompt struct { *Ctx - location int - prefix string - prefixLen int + anchor VerticalAnchor // AnchorTop or AnchorBottom + anchorOffset int // offset this many lines from the anchor + prefix string + prefixLen int } -func NewUserPrompt(ctx *Ctx) *UserPrompt { +func NewUserPrompt(ctx *Ctx, anchor VerticalAnchor, anchorOffset int) *UserPrompt { prefix := ctx.config.Prompt if len(prefix) <= 0 { // default prefix = "QUERY>" @@ -63,16 +71,30 @@ func NewUserPrompt(ctx *Ctx) *UserPrompt { prefixLen := runewidth.StringWidth(prefix) return &UserPrompt{ - Ctx: ctx, - location: 0, // effectively, the line number where the prompt is going to be displayed at - prefix: prefix, - prefixLen: prefixLen, + Ctx: ctx, + anchor: anchor, + anchorOffset: anchorOffset, + prefix: prefix, + prefixLen: prefixLen, } } func (u UserPrompt) Draw() { // print "QUERY>" - printScreen(0, u.location, u.config.Style.BasicFG(), u.config.Style.BasicBG(), u.prefix, false) + + _, h := termbox.Size() + + var location int + switch u.anchor { + case AnchorTop: + location = u.anchorOffset + case AnchorBottom: + location = h - u.anchorOffset - 1 // -1 is required because y is 0 base, but h is 1 base + default: + panic("Unknown anchor type!") + } + + printScreen(0, location, u.config.Style.BasicFG(), u.config.Style.BasicBG(), u.prefix, false) if u.caretPos <= 0 { u.caretPos = 0 // sanity @@ -88,9 +110,9 @@ func (u UserPrompt) Draw() { bg := u.config.Style.QueryBG() qs := string(u.query) ql := runewidth.StringWidth(qs) - printScreen(u.prefixLen+1, u.location, fg, bg, qs, false) - printScreen(u.prefixLen+1+ql, u.location, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false) - printScreen(u.prefixLen+1+ql+1, u.location, fg, bg, "", true) + printScreen(u.prefixLen+1, location, fg, bg, qs, false) + printScreen(u.prefixLen+1+ql, location, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false) + printScreen(u.prefixLen+1+ql+1, location, fg, bg, "", true) } else { // the caret is in the middle of the string prev := 0 @@ -101,7 +123,7 @@ func (u UserPrompt) Draw() { fg |= termbox.AttrReverse bg |= termbox.AttrReverse } - termbox.SetCell(u.prefixLen+1+prev, u.location, r, fg, bg) + termbox.SetCell(u.prefixLen+1+prev, location, r, fg, bg) prev += runewidth.RuneWidth(r) } } @@ -109,7 +131,7 @@ func (u UserPrompt) Draw() { width, _ := termbox.Size() pmsg := fmt.Sprintf("%s [%d/%d]", u.Matcher().String(), u.currentPage.index, u.maxPage) - printScreen(width-runewidth.StringWidth(pmsg), u.location, u.config.Style.BasicFG(), u.config.Style.BasicBG(), pmsg, false) + printScreen(width-runewidth.StringWidth(pmsg), location, u.config.Style.BasicFG(), u.config.Style.BasicBG(), pmsg, false) } // StatusBar draws the status message bar @@ -162,11 +184,11 @@ func (s *StatusBar) PrintStatus(msg string) { bgAttr := s.config.Style.BasicBG() if w > width { - printScreen(0, h-2, fgAttr, bgAttr, string(pad), false) + printScreen(0, h-1, fgAttr, bgAttr, string(pad), false) } if width > 0 { - printScreen(w-width, h-2, fgAttr|termbox.AttrReverse|termbox.AttrBold, bgAttr|termbox.AttrReverse, msg, false) + printScreen(w-width, h-1, fgAttr|termbox.AttrReverse|termbox.AttrBold, bgAttr|termbox.AttrReverse, msg, false) } termbox.Flush() } @@ -174,7 +196,7 @@ func (s *StatusBar) PrintStatus(msg string) { type ListArea struct { *Ctx sortTopDown bool - start int + start int } func NewListArea(ctx *Ctx) *ListArea { @@ -200,7 +222,7 @@ func (l *ListArea) Draw(targets []Match, perPage int) { var fgAttr, bgAttr termbox.Attribute for n := 0; n < perPage; n++ { switch { - case n+currentPage.offset == l.currentLine - l.start: + case n+currentPage.offset == l.currentLine-l.start: fgAttr = l.config.Style.SelectedFG() bgAttr = l.config.Style.SelectedBG() case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset): @@ -267,10 +289,10 @@ type BottomUpLayout struct { func NewDefaultLayout(ctx *Ctx) *DefaultLayout { return &DefaultLayout{ &basicLayout{ - Ctx: ctx, + Ctx: ctx, StatusBar: NewStatusBar(ctx), - prompt: NewUserPrompt(ctx), - list: NewListArea(ctx), + prompt: NewUserPrompt(ctx, AnchorTop, 0), + list: NewListArea(ctx), }, } } From 789ee803c30adf09ef60280d33a77986e6baca34 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 18:07:19 +0900 Subject: [PATCH 07/19] Change everything to use anchors via AnchorSettings --- layout.go | 149 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 83 insertions(+), 66 deletions(-) diff --git a/layout.go b/layout.go index 4a2313f..719ea3e 100644 --- a/layout.go +++ b/layout.go @@ -54,13 +54,34 @@ func printScreen(x, y int, fg, bg termbox.Attribute, msg string, fill bool) { } } +type AnchorSettings struct { + anchor VerticalAnchor // AnchorTop or AnchorBottom + anchorOffset int // offset this many lines from the anchor +} + +// AnchorPosition returns the starting y-offset, based on the +// anchor type and offset +func (as AnchorSettings) AnchorPosition() int { + var pos int + switch as.anchor { + case AnchorTop: + pos = as.anchorOffset + case AnchorBottom: + _, h := termbox.Size() + pos = h - as.anchorOffset - 1 // -1 is required because y is 0 base, but h is 1 base + default: + panic("Unknown anchor type!") + } + + return pos +} + // UserPrompt draws the prompt line type UserPrompt struct { *Ctx - anchor VerticalAnchor // AnchorTop or AnchorBottom - anchorOffset int // offset this many lines from the anchor - prefix string - prefixLen int + *AnchorSettings + prefix string + prefixLen int } func NewUserPrompt(ctx *Ctx, anchor VerticalAnchor, anchorOffset int) *UserPrompt { @@ -71,29 +92,17 @@ func NewUserPrompt(ctx *Ctx, anchor VerticalAnchor, anchorOffset int) *UserPromp prefixLen := runewidth.StringWidth(prefix) return &UserPrompt{ - Ctx: ctx, - anchor: anchor, - anchorOffset: anchorOffset, - prefix: prefix, - prefixLen: prefixLen, + Ctx: ctx, + AnchorSettings: &AnchorSettings{anchor, anchorOffset}, + prefix: prefix, + prefixLen: prefixLen, } } func (u UserPrompt) Draw() { + location := u.AnchorPosition() + // print "QUERY>" - - _, h := termbox.Size() - - var location int - switch u.anchor { - case AnchorTop: - location = u.anchorOffset - case AnchorBottom: - location = h - u.anchorOffset - 1 // -1 is required because y is 0 base, but h is 1 base - default: - panic("Unknown anchor type!") - } - printScreen(0, location, u.config.Style.BasicFG(), u.config.Style.BasicBG(), u.prefix, false) if u.caretPos <= 0 { @@ -137,12 +146,14 @@ func (u UserPrompt) Draw() { // StatusBar draws the status message bar type StatusBar struct { *Ctx + *AnchorSettings clearTimer *time.Timer } -func NewStatusBar(ctx *Ctx) *StatusBar { +func NewStatusBar(ctx *Ctx, anchor VerticalAnchor, anchorOffset int) *StatusBar { return &StatusBar{ ctx, + &AnchorSettings{ anchor, anchorOffset }, nil, } } @@ -163,8 +174,9 @@ func (s *StatusBar) ClearStatus(d time.Duration) { func (s *StatusBar) PrintStatus(msg string) { s.stopTimer() - w, h := termbox.Size() + location := s.AnchorPosition() + w, _ := termbox.Size() width := runewidth.StringWidth(msg) for width > w { _, rw := utf8.DecodeRuneInString(msg) @@ -184,45 +196,39 @@ func (s *StatusBar) PrintStatus(msg string) { bgAttr := s.config.Style.BasicBG() if w > width { - printScreen(0, h-1, fgAttr, bgAttr, string(pad), false) + printScreen(0, location, fgAttr, bgAttr, string(pad), false) } if width > 0 { - printScreen(w-width, h-1, fgAttr|termbox.AttrReverse|termbox.AttrBold, bgAttr|termbox.AttrReverse, msg, false) + printScreen(w-width, location, fgAttr|termbox.AttrReverse|termbox.AttrBold, bgAttr|termbox.AttrReverse, msg, false) } termbox.Flush() } type ListArea struct { *Ctx - sortTopDown bool - start int + *AnchorSettings + sortTopDown bool } -func NewListArea(ctx *Ctx) *ListArea { +func NewListArea(ctx *Ctx, anchor VerticalAnchor, anchorOffset int, sortTopDown bool) *ListArea { return &ListArea{ ctx, - true, - 1, + &AnchorSettings{ anchor, anchorOffset }, + sortTopDown, } } -// given the n-th element to display, calculate which y offset that line should -// be displayed at -func (l *ListArea) calcYLocation(n int) int { - if l.sortTopDown { - return n + l.start - } - return l.start - n -} - func (l *ListArea) Draw(targets []Match, perPage int) { currentPage := l.currentPage + start := l.AnchorPosition() + + var y int var fgAttr, bgAttr termbox.Attribute for n := 0; n < perPage; n++ { switch { - case n+currentPage.offset == l.currentLine-l.start: + case n+currentPage.offset == l.currentLine-start: fgAttr = l.config.Style.SelectedFG() bgAttr = l.config.Style.SelectedBG() case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset): @@ -238,7 +244,11 @@ func (l *ListArea) Draw(targets []Match, perPage int) { break } - y := l.calcYLocation(n) + if l.sortTopDown { + y = n + start + } else { + y = start - n + } target := targets[targetIdx] line := target.Line() @@ -271,33 +281,42 @@ func (l *ListArea) Draw(targets []Match, perPage int) { } } -type basicLayout struct { +// BasicLayout is... the basic layout :) At this point this is the +// only struct for layouts, which means that while the position +// 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 } -// DefaultLayout implements the top-down layout -type DefaultLayout struct { - *basicLayout -} -type BottomUpLayout struct { - *basicLayout -} - -func NewDefaultLayout(ctx *Ctx) *DefaultLayout { - return &DefaultLayout{ - &basicLayout{ - Ctx: ctx, - StatusBar: NewStatusBar(ctx), - prompt: NewUserPrompt(ctx, AnchorTop, 0), - list: NewListArea(ctx), - }, +func NewDefaultLayout(ctx *Ctx) *BasicLayout { + return &BasicLayout{ + Ctx: ctx, + StatusBar: NewStatusBar(ctx, AnchorBottom, 0), + // The prompt is at the top + prompt: NewUserPrompt(ctx, AnchorTop, 0), + // The list area is at the top, after the prompt + // It's also displayed top-to-bottom order + list: NewListArea(ctx, AnchorTop, 1, true), } } -func (l *DefaultLayout) CalculatePage(targets []Match, perPage int) error { +func NewBottomUpLayout(ctx *Ctx) *BasicLayout { + return &BasicLayout{ + Ctx: ctx, + StatusBar: NewStatusBar(ctx, AnchorBottom, 0), + // The prompt is at the bottom, above the status bar + prompt: NewUserPrompt(ctx, AnchorBottom, 1), + // The list area is at the bottom, above the prompt + // IT's displayed in bottom-to-top order + list: NewListArea(ctx, AnchorBottom, 2, false), + } +} + +func (l *BasicLayout) CalculatePage(targets []Match, perPage int) error { CALCULATE_PAGE: currentPage := l.currentPage currentPage.index = ((l.currentLine - 1) / perPage) + 1 @@ -324,11 +343,8 @@ CALCULATE_PAGE: return nil } -func (l *DefaultLayout) DrawScreen(targets []Match) { - fgAttr := l.config.Style.BasicFG() - bgAttr := l.config.Style.BasicBG() - - if err := termbox.Clear(fgAttr, bgAttr); err != nil { +func (l *BasicLayout) DrawScreen(targets []Match) { + if err := termbox.Clear(l.config.Style.BasicFG(), l.config.Style.BasicBG()); err != nil { return } @@ -337,7 +353,7 @@ func (l *DefaultLayout) DrawScreen(targets []Match) { } _, height := termbox.Size() - perPage := height - 4 + perPage := height - 2 // list area is always the display area - 2 lines for prompt and status if err := l.CalculatePage(targets, perPage); err != nil { return @@ -345,6 +361,7 @@ func (l *DefaultLayout) DrawScreen(targets []Match) { l.prompt.Draw() l.list.Draw(targets, perPage) + if err := termbox.Flush(); err != nil { return } From 7ed340e6ade114410437a76c2e4f8f7265586e70 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 18:17:39 +0900 Subject: [PATCH 08/19] Make layout selectable from command line Note: bottom-up is still broken --- cmd/peco/peco.go | 13 +++++++++++++ ctx.go | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/cmd/peco/peco.go b/cmd/peco/peco.go index 0d7448d..2d19f34 100644 --- a/cmd/peco/peco.go +++ b/cmd/peco/peco.go @@ -27,6 +27,7 @@ Options: --initial-index position of the initial index of the selection (0 base) --initial-matcher specify default matcher --prompt specify prompt + --layout specify the layout to use. default is 'top-down' ` os.Stderr.Write([]byte(v)) } @@ -43,6 +44,7 @@ type cmdOptions struct { OptInitialIndex int `long:"initial-index" description:"position of the initial index of the selection (0 base)"` OptInitialMatcher string `long:"initial-matcher" description:"matcher"` OptPrompt string `long:"prompt"` + OptLayout string `long:"layout" description:"layout to be used 'top-down' (default) or 'bottom-up'" default:"top-down"` } // BufferSize returns the specified buffer size. Fulfills peco.CtxOptions @@ -62,6 +64,10 @@ func (o cmdOptions) InitialIndex() int { return 1 } +func (o cmdOptions) LayoutType() string { + return o.OptLayout +} + func main() { var err error var st int @@ -81,6 +87,13 @@ func main() { return } + // XXX silly way to validate. come back later to make validation a bit smarter + if opts.OptLayout != "top-down" && opts.OptLayout != "bottom-up" { + fmt.Fprintf(os.Stderr, "Unknown layout: '%s'\n", opts.OptLayout) + st = 1 + return + } + if opts.OptHelp { showHelp() return diff --git a/ctx.go b/ctx.go index 35e362d..cf099df 100644 --- a/ctx.go +++ b/ctx.go @@ -24,6 +24,9 @@ type CtxOptions interface { // InitialIndex is the line number to put the cursor on // when peco starts InitialIndex() int + + // LayoutType returns the name of the layout to use + LayoutType() string } type PageInfo struct { @@ -54,6 +57,7 @@ type Ctx struct { CurrentMatcher int ExitStatus int selectionRangeStart int + layoutType string wait *sync.WaitGroup } @@ -83,6 +87,7 @@ func NewCtx(o CtxOptions) *Ctx { 0, 0, invalidSelectionRange, + o.LayoutType(), &sync.WaitGroup{}, } } @@ -181,7 +186,16 @@ func (c *Ctx) NewBufferReader(r io.ReadCloser) *BufferReader { } func (c *Ctx) NewView() *View { - return &View{c, NewDefaultLayout(c)} + var layout Layout + switch c.layoutType { + case "top-down": + layout = NewDefaultLayout(c) + case "bottom-up": + layout = NewBottomUpLayout(c) + default: + panic("Unknown layout") + } + return &View{c, layout} } func (c *Ctx) NewFilter() *Filter { From b189d0f6952143e26570bdd80a97a4ee99df90b9 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 18:27:23 +0900 Subject: [PATCH 09/19] Fix the logic to check if a line is selected --- layout.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout.go b/layout.go index 719ea3e..283cbdf 100644 --- a/layout.go +++ b/layout.go @@ -228,7 +228,7 @@ func (l *ListArea) Draw(targets []Match, perPage int) { var fgAttr, bgAttr termbox.Attribute for n := 0; n < perPage; n++ { switch { - case n+currentPage.offset == l.currentLine-start: + case n+currentPage.offset == l.currentLine-1: fgAttr = l.config.Style.SelectedFG() bgAttr = l.config.Style.SelectedBG() case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset): From 04b1427f93955e874884926d568982f4c906b973 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 18:47:22 +0900 Subject: [PATCH 10/19] Fix moving around with arrows --- action.go | 39 ++++++++++++++++++++++-------------- layout.go | 39 ++++++++++++++++++++++++++++++++++++ view.go | 59 +++++++++++++++++-------------------------------------- 3 files changed, 81 insertions(+), 56 deletions(-) diff --git a/action.go b/action.go index 8b6bb3a..9498f93 100644 --- a/action.go +++ b/action.go @@ -76,20 +76,24 @@ func init() { ActionFunc(doKillEndOfLine).Register("KillEndOfLine", termbox.KeyCtrlK) ActionFunc(doKillBeginningOfLine).Register("KillBeginningOfLine", termbox.KeyCtrlU) ActionFunc(doRotateMatcher).Register("RotateMatcher", termbox.KeyCtrlR) - ActionFunc(doSelectNext).Register( - "SelectNext", - termbox.KeyArrowDown, - termbox.KeyCtrlN, - ) + + ActionFunc(doSelectUp).Register("SelectUp", termbox.KeyArrowUp, termbox.KeyCtrlP) + ActionFunc(func(i *Input, ev termbox.Event) { + i.SendStatusMsg("SelectNext is deprecated. Use SelectUp/SelectDown") + doSelectUp(i, ev) + }).Register("SelectNext") + ActionFunc(doSelectNextPage).Register( "SelectNextPage", termbox.KeyArrowRight, ) - ActionFunc(doSelectPrevious).Register( - "SelectPrevious", - termbox.KeyArrowUp, - termbox.KeyCtrlP, - ) + + ActionFunc(doSelectDown).Register("SelectDown", termbox.KeyArrowDown, termbox.KeyCtrlN) + ActionFunc(func(i *Input, ev termbox.Event) { + i.SendStatusMsg("SelectPrevious is deprecated. Use SelectUp/SelectDown") + doSelectDown(i, ev) + }).Register( "SelectPrevious") + ActionFunc(doSelectPreviousPage).Register( "SelectPreviousPage", termbox.KeyArrowLeft, @@ -248,13 +252,13 @@ func doCancel(i *Input, ev termbox.Event) { i.ExitWith(1) } -func doSelectPrevious(i *Input, ev termbox.Event) { - i.SendPaging(ToPrevLine) +func doSelectDown(i *Input, ev termbox.Event) { + i.SendPaging(ToLineBelow) i.DrawMatches(nil) } -func doSelectNext(i *Input, ev termbox.Event) { - i.SendPaging(ToNextLine) +func doSelectUp(i *Input, ev termbox.Event) { + i.SendPaging(ToLineAbove) i.DrawMatches(nil) } @@ -270,7 +274,12 @@ func doSelectNextPage(i *Input, ev termbox.Event) { func doToggleSelectionAndSelectNext(i *Input, ev termbox.Event) { doToggleSelection(i, ev) - doSelectNext(i, ev) + // XXX This is sucky. Fix later + if i.layoutType == "top-down" { + doSelectDown(i, ev) + } else { + doSelectUp(i, ev) + } } func doDeleteBackwardWord(i *Input, _ termbox.Event) { diff --git a/layout.go b/layout.go index 283cbdf..55e1596 100644 --- a/layout.go +++ b/layout.go @@ -20,6 +20,7 @@ type Layout interface { ClearStatus(time.Duration) PrintStatus(string) DrawScreen([]Match) + MovePage(PagingRequest) } // Utility function @@ -366,3 +367,41 @@ func (l *BasicLayout) DrawScreen(targets []Match) { return } } + +func (l *BasicLayout) MovePage(p PagingRequest) { + _, height := termbox.Size() + perPage := height - 2 // list area is always the display area - 2 lines for prompt and status + + switch p { + case ToLineAbove: + if l.list.sortTopDown { + l.currentLine-- + } else { + l.currentLine++ + } + case ToLineBelow: + if l.list.sortTopDown { + l.currentLine++ + } else { + l.currentLine-- + } + case ToPrevPage, ToNextPage: + if p == ToPrevPage { + l.currentLine -= perPage + } else { + l.currentLine += perPage + } + } + + if l.currentLine < 1 { + if l.current != nil { + // Go to last page, if possible + l.currentLine = len(l.current) + } else { + l.currentLine = 1 + } + } else if l.current != nil && l.currentLine > len(l.current) { + l.currentLine = 1 + } +} + diff --git a/view.go b/view.go index e3e7427..4b47353 100644 --- a/view.go +++ b/view.go @@ -1,10 +1,6 @@ package peco -import ( - "time" - - "github.com/nsf/termbox-go" -) +import "time" // View handles the drawing/updating the screen type View struct { @@ -16,12 +12,12 @@ type View struct { type PagingRequest int const ( - // ToNextLine moves the selection to the next line - ToNextLine PagingRequest = iota + // ToLineAbove moves the selection to the line above + ToLineAbove PagingRequest = iota // ToNextPage moves the selection to the next page ToNextPage - // ToPrevLine moves the selection to the previous line - ToPrevLine + // ToLineBelow moves the selection to the line below + ToLineBelow // ToPrevPage moves the selection to the previous page ToPrevPage ) @@ -57,10 +53,7 @@ func (v *View) clearStatus(d time.Duration) { v.layout.ClearStatus(d) } -func (v *View) drawScreen(targets []Match) { - v.mutex.Lock() - defer v.mutex.Unlock() - +func (v *View) drawScreenNoLock(targets []Match) { if targets == nil { if current := v.current; current != nil { targets = v.current @@ -74,32 +67,16 @@ func (v *View) drawScreen(targets []Match) { v.current = targets } -func (v *View) movePage(p PagingRequest) { - _, height := termbox.Size() - perPage := height - 4 - - switch p { - case ToPrevLine: - v.currentLine-- - case ToNextLine: - v.currentLine++ - case ToPrevPage, ToNextPage: - if p == ToPrevPage { - v.currentLine -= perPage - } else { - v.currentLine += perPage - } - } - - if v.currentLine < 1 { - if v.current != nil { - // Go to last page, if possible - v.currentLine = len(v.current) - } else { - v.currentLine = 1 - } - } else if v.current != nil && v.currentLine > len(v.current) { - v.currentLine = 1 - } - v.drawScreen(nil) +func (v *View) drawScreen(targets []Match) { + v.mutex.Lock() + defer v.mutex.Unlock() + v.drawScreenNoLock(targets) +} + +func (v *View) movePage(p PagingRequest) { + v.mutex.Lock() + defer v.mutex.Unlock() + + v.layout.MovePage(p) + v.drawScreenNoLock(nil) } From f9ee817fd5f2f86f10a04c75f431ba2646c0953d Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 16 Aug 2014 21:13:32 +0900 Subject: [PATCH 11/19] Allow setting layout from config file ...So you don't have to type it all the time! --- cmd/peco/peco.go | 13 +++++++------ config.go | 8 +++++++- ctx.go | 10 +++++++--- layout.go | 15 +++++++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/cmd/peco/peco.go b/cmd/peco/peco.go index 2d19f34..4abb077 100644 --- a/cmd/peco/peco.go +++ b/cmd/peco/peco.go @@ -44,7 +44,7 @@ type cmdOptions struct { OptInitialIndex int `long:"initial-index" description:"position of the initial index of the selection (0 base)"` OptInitialMatcher string `long:"initial-matcher" description:"matcher"` OptPrompt string `long:"prompt"` - OptLayout string `long:"layout" description:"layout to be used 'top-down' (default) or 'bottom-up'" default:"top-down"` + OptLayout string `long:"layout" description:"layout to be used 'top-down' (default) or 'bottom-up'"` } // BufferSize returns the specified buffer size. Fulfills peco.CtxOptions @@ -87,11 +87,12 @@ func main() { return } - // XXX silly way to validate. come back later to make validation a bit smarter - if opts.OptLayout != "top-down" && opts.OptLayout != "bottom-up" { - fmt.Fprintf(os.Stderr, "Unknown layout: '%s'\n", opts.OptLayout) - st = 1 - return + if opts.OptLayout != "" { + if ! peco.IsValidLayoutType(opts.OptLayout) { + fmt.Fprintf(os.Stderr, "Unknown layout: '%s'\n", opts.OptLayout) + st = 1 + return + } } if opts.OptHelp { diff --git a/config.go b/config.go index 07ee830..e797028 100644 --- a/config.go +++ b/config.go @@ -20,10 +20,11 @@ type Config struct { // events against user input, but since then this has changed // into something that just records the user's config input Keymap map[string]string `json:"Keymap"` - Matcher string `json:"Matcher"` // Deprecated. + Matcher string `json:"Matcher"` // Deprecated. InitialMatcher string `json:"InitialMatcher"` // Use this instead of Matcher Style *StyleSet `json:"Style"` Prompt string `json:"Prompt"` + Layout string `json:"Layout"` CustomMatcher map[string][]string } @@ -34,6 +35,7 @@ func NewConfig() *Config { InitialMatcher: IgnoreCaseMatch, Style: NewStyleSet(), Prompt: "QUERY>", + Layout: "top-down", } } @@ -51,6 +53,10 @@ func (c *Config) ReadFilename(filename string) error { return err } + if !IsValidLayoutType(c.Layout) { + return fmt.Errorf("invalid layout type: %s", c.Layout) + } + return nil } diff --git a/ctx.go b/ctx.go index cf099df..6db75cf 100644 --- a/ctx.go +++ b/ctx.go @@ -110,6 +110,12 @@ func (c *Ctx) ReadConfig(file string) error { c.SetCurrentMatcher(c.config.InitialMatcher) + if c.layoutType == "" { // Not set yet + if c.config.Layout != "" { + c.layoutType = c.config.Layout + } + } + return nil } @@ -188,12 +194,10 @@ func (c *Ctx) NewBufferReader(r io.ReadCloser) *BufferReader { func (c *Ctx) NewView() *View { var layout Layout switch c.layoutType { - case "top-down": - layout = NewDefaultLayout(c) case "bottom-up": layout = NewBottomUpLayout(c) default: - panic("Unknown layout") + layout = NewDefaultLayout(c) } return &View{c, layout} } diff --git a/layout.go b/layout.go index 55e1596..48b40dc 100644 --- a/layout.go +++ b/layout.go @@ -9,6 +9,21 @@ import ( "github.com/nsf/termbox-go" ) +type LayoutType string +const ( + LayoutTypeTopDown = "top-down" + LayoutTypeBottomUp = "bottom-up" +) + +// IsValidLayoutType checks if a string is a supported layout type +func IsValidLayoutType(v string) bool { + if v == LayoutTypeTopDown || v == LayoutTypeBottomUp { + return true + } + + return false +} + type VerticalAnchor int const ( From 908f724c01a3bded9bb71ff5c4713c1000be88a6 Mon Sep 17 00:00:00 2001 From: lestrrat Date: Sat, 16 Aug 2014 21:50:14 +0900 Subject: [PATCH 12/19] Update README.md --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ad0573..12a2d9e 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,10 @@ Specifies the initial matcher to use upon start up. You should specify the name Specifies the query line's prompt string. When specified, takes precedence over the configuration file's `Prompt` section. The default value is `QUERY>` +### --layout `top-down|bottom-up` + +Specifies the display layout. Default is `top-down`, where query prompt is at the top, followed by the list, then the system status message line. `bottom-up` changes this to the list first (displayed in reverse order), the query prompt, and then the system status message line. + Configuration File ================== @@ -267,8 +271,10 @@ Some keys just... don't map correctly / too easily for various reasons. Here, we | peco.DeleteAll | Delete all entered characters | | peco.SelectPreviousPage | Jumps to previous page | | peco.SelectNextPage | Jumps to next page| -| peco.SelectPrevious | Selects previous line | -| peco.SelectNext | Selects next line | +| peco.SelectUp | Moves the selected line cursor to one line above | +| peco.SelectDown | Moves the selected line cursor to one line below | +| peco.SelectPrevious | (DEPRECATED) Alias to SelectUp | +| peco.SelectNext | (DEPRECATED) Alias to SelectDown | | peco.ToggleSelection | Selects the current line, and saves it | | peco.ToggleSelectionAndSelectNext | Selects the current line, saves it, and proceeds to the next line | | peco.ToggleRangeMode | Start selecting by range, or append selecting range to selections | @@ -398,6 +404,10 @@ Specifies the matcher name to start peco with. You should specify the name of th Note: `Matcher` key has been deprecated in favor of `InitialMatcher`. `Matcher` will be unavailable in peco 0.3.0 +## Layout + +See --layout. + Hacking ======= From 74f4c46ce8a3656adfe2a722f93c0b3e14ed95f2 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 17 Aug 2014 08:20:16 +0900 Subject: [PATCH 13/19] Implement ScrollPageUp/ScrollPageDown Based on comment at https://github.com/peco/peco/pull/172#issuecomment-52393224 --- README.md | 16 +++++++++------- action.go | 26 ++++++++++++++------------ layout.go | 35 ++++++++++++++++++++--------------- view.go | 8 ++++---- 4 files changed, 47 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 12a2d9e..316dee2 100644 --- a/README.md +++ b/README.md @@ -181,8 +181,8 @@ Example: ```json { "Keymap": { - "M-v": "peco.SelectPreviousPage", - "C-v": "peco.SelectNextPage", + "M-v": "peco.ScrollPageDown", + "C-v": "peco.ScrollPageUp", "C-x,C-c": "peco.Cancel" } } @@ -201,9 +201,9 @@ As of v0.2.1, you can create custom combined actions. For example, if you find y "Action": { "foo.SelectFour": [ "peco.ToggleRangeMode", - "peco.SelectNext", - "peco.SelectNext", - "peco.SelectNext", + "peco.SelectDown", + "peco.SelectDown", + "peco.SelectDown", "peco.ToggleRangeMode" ] }, @@ -269,8 +269,10 @@ Some keys just... don't map correctly / too easily for various reasons. Here, we | peco.DeleteBackwardWord | Delete one word backward | | peco.KillEndOfLine | Delete the characters under the cursor until the end of the line | | peco.DeleteAll | Delete all entered characters | -| peco.SelectPreviousPage | Jumps to previous page | -| peco.SelectNextPage | Jumps to next page| +| peco.SelectPreviousPage | (DEPRECATED) Alias to ScrollPageUp | +| peco.SelectNextPage | (DEPRECATED) Alias to ScrollPageDown | +| peco.ScrollPageDown | Moves the selected line cursor for an entire page, downwards | +| peco.ScrollPageUp | Moves the selected line cursor for an entire page, upwards | | peco.SelectUp | Moves the selected line cursor to one line above | | peco.SelectDown | Moves the selected line cursor to one line below | | peco.SelectPrevious | (DEPRECATED) Alias to SelectUp | diff --git a/action.go b/action.go index 9498f93..ae727c9 100644 --- a/action.go +++ b/action.go @@ -83,10 +83,11 @@ func init() { doSelectUp(i, ev) }).Register("SelectNext") - ActionFunc(doSelectNextPage).Register( - "SelectNextPage", - termbox.KeyArrowRight, - ) + ActionFunc(doScrollPageDown).Register("ScrollPageDown", termbox.KeyArrowRight) + ActionFunc(func(i *Input, ev termbox.Event) { + i.SendStatusMsg("SelectNextPage is deprecated. Use ScrollPageDown/ScrollPageUp") + doScrollPageDown(i, ev) + }).Register("SelectNextPage") ActionFunc(doSelectDown).Register("SelectDown", termbox.KeyArrowDown, termbox.KeyCtrlN) ActionFunc(func(i *Input, ev termbox.Event) { @@ -94,10 +95,11 @@ func init() { doSelectDown(i, ev) }).Register( "SelectPrevious") - ActionFunc(doSelectPreviousPage).Register( - "SelectPreviousPage", - termbox.KeyArrowLeft, - ) + ActionFunc(doScrollPageUp).Register("ScrollPageUp", termbox.KeyArrowLeft) + ActionFunc(func(i *Input, ev termbox.Event) { + i.SendStatusMsg("SelectPreviousPage is deprecated. Uselect ScrollPageDown/ScrollPageUp") + doScrollPageUp(i, ev) + }).Register("SelectPreviousPage") ActionFunc(doToggleSelection).Register("ToggleSelection") ActionFunc(doToggleSelectionAndSelectNext).Register( @@ -262,13 +264,13 @@ func doSelectUp(i *Input, ev termbox.Event) { i.DrawMatches(nil) } -func doSelectPreviousPage(i *Input, ev termbox.Event) { - i.SendPaging(ToPrevPage) +func doScrollPageUp(i *Input, ev termbox.Event) { + i.SendPaging(ToScrollPageUp) i.DrawMatches(nil) } -func doSelectNextPage(i *Input, ev termbox.Event) { - i.SendPaging(ToNextPage) +func doScrollPageDown(i *Input, ev termbox.Event) { + i.SendPaging(ToScrollPageDown) i.DrawMatches(nil) } diff --git a/layout.go b/layout.go index 48b40dc..e548b21 100644 --- a/layout.go +++ b/layout.go @@ -383,28 +383,33 @@ func (l *BasicLayout) DrawScreen(targets []Match) { } } -func (l *BasicLayout) MovePage(p PagingRequest) { +func linesPerPage() int { _, height := termbox.Size() - perPage := height - 2 // list area is always the display area - 2 lines for prompt and status + return height - 2 // list area is always the display area - 2 lines for prompt and status +} - switch p { - case ToLineAbove: - if l.list.sortTopDown { +func (l *BasicLayout) MovePage(p PagingRequest) { + if l.list.sortTopDown { + switch p { + case ToLineAbove: l.currentLine-- - } else { + case ToLineBelow: l.currentLine++ + case ToScrollPageDown: + l.currentLine += linesPerPage() + case ToScrollPageUp: + l.currentLine -= linesPerPage() } - case ToLineBelow: - if l.list.sortTopDown { + } else { + switch p { + case ToLineAbove: l.currentLine++ - } else { + case ToLineBelow: l.currentLine-- - } - case ToPrevPage, ToNextPage: - if p == ToPrevPage { - l.currentLine -= perPage - } else { - l.currentLine += perPage + case ToScrollPageDown: + l.currentLine -= linesPerPage() + case ToScrollPageUp: + l.currentLine += linesPerPage() } } diff --git a/view.go b/view.go index 4b47353..c1f834b 100644 --- a/view.go +++ b/view.go @@ -14,12 +14,12 @@ type PagingRequest int const ( // ToLineAbove moves the selection to the line above ToLineAbove PagingRequest = iota - // ToNextPage moves the selection to the next page - ToNextPage + // ToScrollPageDown moves the selection to the next page + ToScrollPageDown // ToLineBelow moves the selection to the line below ToLineBelow - // ToPrevPage moves the selection to the previous page - ToPrevPage + // ToScrollPageUp moves the selection to the previous page + ToScrollPageUp ) // Loop receives requests to update the screen From 0aa077bfa26a2b43310ab50edcd019970368de86 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 17 Aug 2014 17:51:25 +0900 Subject: [PATCH 14/19] Jesus, what am I, a CS 101 student? --- layout.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/layout.go b/layout.go index e548b21..348e5f3 100644 --- a/layout.go +++ b/layout.go @@ -17,11 +17,7 @@ const ( // IsValidLayoutType checks if a string is a supported layout type func IsValidLayoutType(v string) bool { - if v == LayoutTypeTopDown || v == LayoutTypeBottomUp { - return true - } - - return false + return v == LayoutTypeTopDown || v == LayoutTypeBottomUp } type VerticalAnchor int From 91eefefa24494519836c1481fe6473e6f8c70913 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 17 Aug 2014 18:01:29 +0900 Subject: [PATCH 15/19] Fix ToggleSelectionAndSelectNext This action was doing the right thing, but the changes in the layout was looking at the wrong line number --- layout.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout.go b/layout.go index 348e5f3..7fdc7d1 100644 --- a/layout.go +++ b/layout.go @@ -243,7 +243,7 @@ func (l *ListArea) Draw(targets []Match, perPage int) { case n+currentPage.offset == l.currentLine-1: fgAttr = l.config.Style.SelectedFG() bgAttr = l.config.Style.SelectedBG() - case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset): + case l.selection.Has(n+currentPage.offset+1) || l.SelectedRange().Has(n+currentPage.offset+1): fgAttr = l.config.Style.SavedSelectionFG() bgAttr = l.config.Style.SavedSelectionBG() default: From 27cfbfebde1e0e84f00fbf981f969bab17847780 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 17 Aug 2014 18:02:09 +0900 Subject: [PATCH 16/19] Use batch mode just in case --- action.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/action.go b/action.go index ae727c9..dcca8ee 100644 --- a/action.go +++ b/action.go @@ -93,7 +93,7 @@ func init() { ActionFunc(func(i *Input, ev termbox.Event) { i.SendStatusMsg("SelectPrevious is deprecated. Use SelectUp/SelectDown") doSelectDown(i, ev) - }).Register( "SelectPrevious") + }).Register("SelectPrevious") ActionFunc(doScrollPageUp).Register("ScrollPageUp", termbox.KeyArrowLeft) ActionFunc(func(i *Input, ev termbox.Event) { @@ -275,13 +275,15 @@ func doScrollPageDown(i *Input, ev termbox.Event) { } func doToggleSelectionAndSelectNext(i *Input, ev termbox.Event) { - doToggleSelection(i, ev) - // XXX This is sucky. Fix later - if i.layoutType == "top-down" { - doSelectDown(i, ev) - } else { - doSelectUp(i, ev) - } + i.Batch(func() { + doToggleSelection(i, ev) + // XXX This is sucky. Fix later + if i.layoutType == "top-down" { + doSelectDown(i, ev) + } else { + doSelectUp(i, ev) + } + }) } func doDeleteBackwardWord(i *Input, _ termbox.Event) { From 23a43811564522338e9f35e773ea22ddf4a5f6d1 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 17 Aug 2014 18:06:52 +0900 Subject: [PATCH 17/19] Fix README As proposed by https://github.com/peco/peco/pull/172#commitcomment-7414889 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 316dee2..eae6e33 100644 --- a/README.md +++ b/README.md @@ -181,8 +181,8 @@ Example: ```json { "Keymap": { - "M-v": "peco.ScrollPageDown", - "C-v": "peco.ScrollPageUp", + "M-v": "peco.ScrollPageUp", + "C-v": "peco.ScrollPageDown", "C-x,C-c": "peco.Cancel" } } From 36549186c62d6f7492683c87aa3cd7004c43912a Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 17 Aug 2014 18:16:30 +0900 Subject: [PATCH 18/19] Add notes to Changes file --- Changes | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Changes b/Changes index c1d75a9..ea0eec6 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,20 @@ Changes ======= +v0.2.5 + Features + * Add --layout option, which allows you to switch between `top-down` + and `bottom-up` layout mode. This is equivalent of percol's + `--prompt-bottom --result-bottom-up`. Default is `top-down`. + The same option can be specified in the config file as "Layout" + Miscellaneous + * Because of the layout option, SelectNext/SelectPrevious and + SelectNextPage/SelectPreviousPage no longer made sense. + Now all of these are DEPRECATED, and are aliases to different + action names. See the README for the details. + In particular, you would need to configure your key bindings + using these if you want to use the `bottom-up` layout + v0.2.4 - 13 Aug 2014 Features * Add --initial-matcher command line option to specify which From 3ccdc35c40b6479b508c9c0148a10be5d54c9295 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 17 Aug 2014 18:19:53 +0900 Subject: [PATCH 19/19] Remove hardcoded height - 2 and use linesPerPage() --- layout.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/layout.go b/layout.go index 7fdc7d1..655ebe6 100644 --- a/layout.go +++ b/layout.go @@ -364,8 +364,7 @@ func (l *BasicLayout) DrawScreen(targets []Match) { l.currentLine = len(targets) } - _, height := termbox.Size() - perPage := height - 2 // list area is always the display area - 2 lines for prompt and status + perPage := linesPerPage() if err := l.CalculatePage(targets, perPage); err != nil { return