diff --git a/samples/go/decent/ipfs-chat/main.go b/samples/go/decent/ipfs-chat/main.go index ba6d26d638..7514c9e2f6 100644 --- a/samples/go/decent/ipfs-chat/main.go +++ b/samples/go/decent/ipfs-chat/main.go @@ -99,7 +99,7 @@ func runClient(ipfsSpec string, cInfo lib.ClientInfo) { t.ResetAuthors(ds) t.UpdateMessages(ds, nil, nil) - go lib.ProcessChatEvents(node, ds, events, &t, cInfo) + go lib.ProcessChatEvents(node, ds, events, t, cInfo) go lib.ReceiveMessages(node, events, cInfo) if err := t.Gui.MainLoop(); err != nil && err != gocui.ErrQuit { diff --git a/samples/go/decent/lib/datapager.go b/samples/go/decent/lib/datapager.go index fa982b305c..4b694b0496 100644 --- a/samples/go/decent/lib/datapager.go +++ b/samples/go/decent/lib/datapager.go @@ -6,6 +6,7 @@ package lib import ( "fmt" + "strings" "github.com/attic-labs/noms/go/datas" "github.com/attic-labs/noms/go/marshal" @@ -20,16 +21,20 @@ type dataPager struct { terms []string } -var dp dataPager +func NewDataPager(ds datas.Dataset, mkChan chan types.String, doneChan chan struct{}, msgs types.Map, terms []string) *dataPager { + return &dataPager{ + dataset: ds, + msgKeyChan: mkChan, + doneChan: doneChan, + msgMap: msgs, + terms: terms, + } +} func (dp *dataPager) Close() { dp.doneChan <- struct{}{} } -func (dp *dataPager) IsEmpty() bool { - return dp.msgKeyChan == nil && dp.doneChan == nil -} - func (dp *dataPager) Next() (string, bool) { msgKey := <-dp.msgKeyChan if msgKey == "" { @@ -47,3 +52,16 @@ func (dp *dataPager) Next() (string, bool) { s2 := highlightTerms(s1, dp.terms) return s2, true } + +func (dp *dataPager) Prepend(lines []string, target int) ([]string, bool) { + new := []string{} + m, ok := dp.Next() + if !ok { + return lines, false + } + for ; ok && len(new) < target; m, ok = dp.Next() { + new1 := strings.Split(m, "\n") + new = append(new1, new...) + } + return append(new, lines...), true +} diff --git a/samples/go/decent/lib/termui.go b/samples/go/decent/lib/termui.go index 043912c641..8bd8d9d77b 100644 --- a/samples/go/decent/lib/termui.go +++ b/samples/go/decent/lib/termui.go @@ -19,10 +19,11 @@ import ( ) const ( - allViews = "" - usersView = "users" - messageView = "messages" - inputView = "input" + allViews = "" + usersView = "users" + messageView = "messages" + inputView = "input" + linestofetch = 50 searchPrefix = "/s" quitPrefix = "/q" @@ -31,6 +32,8 @@ const ( type TermUI struct { Gui *gocui.Gui InSearch bool + lines []string + dp *dataPager } var ( @@ -38,7 +41,7 @@ var ( firstLayout = true ) -func CreateTermUI(events chan ChatEvent) TermUI { +func CreateTermUI(events chan ChatEvent) *TermUI { g, err := gocui.NewGui(gocui.Output256) d.PanicIfError(err) @@ -51,12 +54,15 @@ func CreateTermUI(events chan ChatEvent) TermUI { } g.SetManagerFunc(relayout) - d.PanicIfError(g.SetKeybinding(allViews, gocui.KeyF1, gocui.ModNone, debugInfo)) + termUI := new(TermUI) + termUI.Gui = g + + d.PanicIfError(g.SetKeybinding(allViews, gocui.KeyF1, gocui.ModNone, debugInfo(termUI))) d.PanicIfError(g.SetKeybinding(allViews, gocui.KeyCtrlC, gocui.ModNone, quit)) d.PanicIfError(g.SetKeybinding(allViews, gocui.KeyCtrlC, gocui.ModAlt, quitWithStack)) d.PanicIfError(g.SetKeybinding(allViews, gocui.KeyTab, gocui.ModNone, nextView)) - d.PanicIfError(g.SetKeybinding(messageView, gocui.KeyArrowUp, gocui.ModNone, arrowUp)) - d.PanicIfError(g.SetKeybinding(messageView, gocui.KeyArrowDown, gocui.ModNone, arrowDown)) + d.PanicIfError(g.SetKeybinding(messageView, gocui.KeyArrowUp, gocui.ModNone, arrowUp(termUI))) + d.PanicIfError(g.SetKeybinding(messageView, gocui.KeyArrowDown, gocui.ModNone, arrowDown(termUI))) d.PanicIfError(g.SetKeybinding(inputView, gocui.KeyEnter, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) (err error) { defer func() { v.Clear() @@ -79,16 +85,16 @@ func CreateTermUI(events chan ChatEvent) TermUI { return })) - return TermUI{Gui: g, InSearch: false} + return termUI } -func (t TermUI) Close() { +func (t *TermUI) Close() { dbg.Debug("Closing gui") t.Gui.Close() } -func (t TermUI) UpdateMessagesFromSync(ds datas.Dataset) { - if t.InSearch || !textScrolledToEnd(t.Gui) { +func (t *TermUI) UpdateMessagesFromSync(ds datas.Dataset) { + if t.InSearch || !t.textScrolledToEnd() { t.Gui.Execute(func(g *gocui.Gui) (err error) { updateViewTitle(g, messageView, "messages (NEW!)") return @@ -98,7 +104,7 @@ func (t TermUI) UpdateMessagesFromSync(ds datas.Dataset) { } } -func (t TermUI) Layout() error { +func (t *TermUI) Layout() error { return layout(t.Gui) } @@ -138,47 +144,34 @@ func layout(g *gocui.Gui) error { return nil } -func (t TermUI) UpdateMessages(ds datas.Dataset, filterIds *types.Map, terms []string) error { +func (t *TermUI) UpdateMessages(ds datas.Dataset, filterIds *types.Map, terms []string) error { defer dbg.BoxF("updateMessages")() t.ResetAuthors(ds) v, err := t.Gui.View(messageView) d.PanicIfError(err) v.Clear() + t.lines = []string{} v.SetOrigin(0, 0) _, winHeight := v.Size() - if !dp.IsEmpty() { - dp.Close() + if t.dp != nil { + t.dp.Close() } doneChan := make(chan struct{}) msgMap, msgKeyChan, err := ListMessages(ds, filterIds, doneChan) d.PanicIfError(err) - dp = dataPager{ - dataset: ds, - msgKeyChan: msgKeyChan, - doneChan: doneChan, - msgMap: msgMap, - terms: terms, - } + t.dp = NewDataPager(ds, msgKeyChan, doneChan, msgMap, terms) + t.lines, _ = t.dp.Prepend(t.lines, math.MaxInt(linestofetch, winHeight+10)) - items := []string{} - for len(items) < winHeight { - m, ok := dp.Next() - if !ok { - break - } - items = append([]string{m}, items...) - } - - for _, s := range items { + for _, s := range t.lines { fmt.Fprintf(v, "%s\n", s) } return nil } -func (t TermUI) ResetAuthors(ds datas.Dataset) { +func (t *TermUI) ResetAuthors(ds datas.Dataset) { v, err := t.Gui.View(usersView) d.PanicIfError(err) v.Clear() @@ -187,7 +180,7 @@ func (t TermUI) ResetAuthors(ds datas.Dataset) { } } -func (t TermUI) UpdateMessagesAsync(ds datas.Dataset, sids *types.Map, terms []string) { +func (t *TermUI) UpdateMessagesAsync(ds datas.Dataset, sids *types.Map, terms []string) { t.Gui.Execute(func(_ *gocui.Gui) error { err := t.UpdateMessages(ds, sids, terms) d.PanicIfError(err) @@ -195,51 +188,35 @@ func (t TermUI) UpdateMessagesAsync(ds datas.Dataset, sids *types.Map, terms []s }) } -func prependMessages(v *gocui.View) int { - buf := viewBuffer(v) - if len(buf) == 0 { - return 0 - } - - msg, ok := dp.Next() - if !ok { - return 0 - } - - v.Clear() - fmt.Fprintf(v, "%s\n", msg) - fmt.Fprintf(v, "%s", highlightTerms(buf, dp.terms)) - return countLines(msg) + countLines(buf) -} - -func scrollView(v *gocui.View, dy, lineCnt int) { +func (t *TermUI) scrollView(v *gocui.View, dy int) { // Get the size and position of the view. - _, height := v.Size() - ox1, oy1 := v.Origin() - cx1, cy1 := v.Cursor() + lineCnt := len(t.lines) + _, windowHeight := v.Size() + ox, oy := v.Origin() + cx, cy := v.Cursor() // maxCy will either be the height of the screen - 1, or in the case that // the there aren't enough lines to fill the screen, it will be the // lineCnt - origin - newCy := cy1 + dy - maxCy := math.MinInt(lineCnt-oy1, height-1) + newCy := cy + dy + maxCy := math.MinInt(lineCnt-oy, windowHeight-1) // If the newCy doesn't require scrolling, then just move the cursor. if newCy >= 0 && newCy < maxCy { - v.MoveCursor(cx1, dy, false) + v.MoveCursor(cx, dy, false) return } // If the cursor is already at the bottom of the screen and there are no // lines left to scroll up, then we're at the bottom. - if newCy >= maxCy && oy1 >= lineCnt-height { + if newCy >= maxCy && oy >= lineCnt-windowHeight { // Set autoscroll to normal again. v.Autoscroll = true } else { // The cursor is already at the bottom or top of the screen so scroll // the text v.Autoscroll = false - v.SetOrigin(ox1, oy1+dy) + v.SetOrigin(ox, oy+dy) } } @@ -256,30 +233,45 @@ func quitWithStack(_ *gocui.Gui, _ *gocui.View) error { return gocui.ErrQuit } -func arrowUp(_ *gocui.Gui, v *gocui.View) error { - lineCnt := countLines(viewBuffer(v)) - if _, oy := v.Origin(); oy == 0 { - lineCnt = prependMessages(v) +func arrowUp(t *TermUI) func(*gocui.Gui, *gocui.View) error { + return func(_ *gocui.Gui, v *gocui.View) error { + lineCnt := len(t.lines) + ox, oy := v.Origin() + if oy == 0 { + var ok bool + t.lines, ok = t.dp.Prepend(t.lines, linestofetch) + if ok { + v.Clear() + for _, s := range t.lines { + fmt.Fprintf(v, "%s\n", s) + } + c1 := len(t.lines) + v.SetOrigin(ox, c1-lineCnt) + } + } + t.scrollView(v, -1) + return nil } - scrollView(v, -1, lineCnt) - return nil } -func arrowDown(_ *gocui.Gui, v *gocui.View) error { - lineCnt := countLines(viewBuffer(v)) - scrollView(v, 1, lineCnt) - return nil +func arrowDown(t *TermUI) func(*gocui.Gui, *gocui.View) error { + return func(_ *gocui.Gui, v *gocui.View) error { + t.scrollView(v, 1) + return nil + } } -func debugInfo(g *gocui.Gui, _ *gocui.View) error { - msgView, _ := g.View(messageView) - w, h := msgView.Size() - dbg.Debug("info, window size:(%d, %d), lineCnt: %d", w, h, countLines(viewBuffer(msgView))) - cx, cy := msgView.Cursor() - ox, oy := msgView.Origin() - dbg.Debug("info, origin: (%d,%d), cursor: (%d,%d)", ox, oy, cx, cy) - dbg.Debug("info, view buffer:\n%s", highlightTerms(viewBuffer(msgView), dp.terms)) - return nil +func debugInfo(t *TermUI) func(*gocui.Gui, *gocui.View) error { + return func(g *gocui.Gui, _ *gocui.View) error { + msgView, _ := g.View(messageView) + w, h := msgView.Size() + dbg.Debug("info, window size:(%d, %d), lineCnt: %d", w, h, len(t.lines)) + cx, cy := msgView.Cursor() + ox, oy := msgView.Origin() + dbg.Debug("info, origin: (%d,%d), cursor: (%d,%d)", ox, oy, cx, cy) + dbg.Debug("info, view buffer:\n%s", highlightTerms(viewBuffer(msgView), t.dp.terms)) + return nil + } } func viewBuffer(v *gocui.View) string { @@ -290,10 +282,6 @@ func viewBuffer(v *gocui.View) string { return buf } -func countLines(s string) int { - return strings.Count(s, "\n") -} - func nextView(g *gocui.Gui, v *gocui.View) (err error) { nextName := nextViewName(v.Name()) if _, err = g.SetCurrentView(nextName); err != nil { @@ -312,15 +300,15 @@ func nextViewName(currentView string) string { return viewNames[0] } -func textScrolledToEnd(g *gocui.Gui) bool { - v, err := g.View(messageView) +func (t *TermUI) textScrolledToEnd() bool { + v, err := t.Gui.View(messageView) if err != nil { // doubt this will ever happen, if it does just assume we're at bottom return true } _, oy := v.Origin() _, h := v.Size() - lc := countLines(viewBuffer(v)) + lc := len(t.lines) dbg.Debug("textScrolledToEnd, oy: %d, h: %d, lc: %d, lc-oy: %d, res: %t", oy, h, lc, lc-oy, lc-oy <= h) return lc-oy <= h } diff --git a/samples/go/decent/p2p-chat/main.go b/samples/go/decent/p2p-chat/main.go index 82d26aaa91..5a8f9662cc 100644 --- a/samples/go/decent/p2p-chat/main.go +++ b/samples/go/decent/p2p-chat/main.go @@ -83,7 +83,7 @@ func runClient(cInfo lib.ClientInfo) { t.ResetAuthors(ds) t.UpdateMessages(ds, nil, nil) - go lib.ProcessChatEvents(node, ds, events, &t, cInfo) + go lib.ProcessChatEvents(node, ds, events, t, cInfo) go lib.ReceiveMessages(node, events, cInfo) if err := t.Gui.MainLoop(); err != nil && err != gocui.ErrQuit {