Permalink
Browse files

Make termio scrolling more efficient

  • Loading branch information...
willhite committed Oct 28, 2017
1 parent 7ebaacf commit 76400c6b710112a3ec35cb963c3eea1bb13d9dd3
@@ -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 {
@@ -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
}
@@ -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,14 +32,16 @@ const (
type TermUI struct {
Gui *gocui.Gui
InSearch bool
lines []string
dp *dataPager
}
var (
viewNames = []string{usersView, messageView, inputView}
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,
}
items := []string{}
for len(items) < winHeight {
m, ok := dp.Next()
if !ok {
break
}
items = append([]string{m}, items...)
}
t.dp = NewDataPager(ds, msgKeyChan, doneChan, msgMap, terms)
t.lines, _ = t.dp.Prepend(t.lines, math.MaxInt(linestofetch, winHeight+10))
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,59 +180,43 @@ 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)
return nil
})
}
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
}
@@ -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 {

0 comments on commit 76400c6

Please sign in to comment.