Browse Source

feed: make feed stateful and simplify ui/ui.go

Benton Edmondson 1 year ago
parent
commit
784c535b88
3 changed files with 134 additions and 39 deletions
  1. 35 8
      feed/feed.go
  2. 81 4
      feed/feed_test.go
  3. 18 27
      ui/ui.go

+ 35 - 8
feed/feed.go

@@ -11,6 +11,8 @@ type Feed struct {
 	// exclusive bounds
 	upperBound int
 	lowerBound int
+
+	index int
 }
 
 func CreateEmpty() *Feed {
@@ -18,6 +20,7 @@ func CreateEmpty() *Feed {
 		feed:       map[int]pub.Tangible{},
 		upperBound: 0,
 		lowerBound: 0,
+		index: 0,
 	}
 }
 
@@ -28,6 +31,7 @@ func Create(input pub.Tangible) *Feed {
 		},
 		upperBound: 1,
 		lowerBound: -1,
+		index: 0,
 	}
 }
 
@@ -36,6 +40,7 @@ func CreateAndAppend(input []pub.Tangible) *Feed {
 		feed: map[int]pub.Tangible{},
 	}
 	f.upperBound = 1
+	f.index = 1
 	f.Append(input)
 	return f
 }
@@ -54,18 +59,40 @@ func (f *Feed) Prepend(input []pub.Tangible) {
 	f.lowerBound -= len(input)
 }
 
-func (f *Feed) Get(index int) pub.Tangible {
-	if !f.Contains(index) {
-		panic(fmt.Sprintf("indexing feed at %d whereas bounds are %d and %d", index, f.lowerBound, f.upperBound))
+func (f *Feed) Get(offset int) pub.Tangible {
+	if !f.Contains(offset) {
+		panic(fmt.Sprintf("indexing feed at %d whereas bounds are %d and %d", f.index + offset, f.lowerBound, f.upperBound))
+	}
+
+	return f.feed[f.index + offset]
+}
+
+func (f *Feed) Current() pub.Tangible {
+	return f.feed[f.index]
+}
+
+func (f *Feed) MoveUp() {
+	if f.Contains(-1) {
+		f.index -= 1
+	}
+}
+
+func (f *Feed) MoveDown() {
+	if f.Contains(1) {
+		f.index += 1
 	}
+}
 
-	return f.feed[index]
+func (f *Feed) MoveToCenter() {
+	if f.Contains(-f.index) {
+		f.index = 0
+	}
 }
 
-func (f *Feed) Contains(index int) bool {
-	return index < f.upperBound && index > f.lowerBound
+func (f *Feed) Contains(offset int) bool {
+	return f.index + offset < f.upperBound && f.index + offset> f.lowerBound
 }
 
-func (f *Feed) IsEmpty() bool {
-	return f.upperBound == 0 && f.lowerBound == 0
+func (f *Feed) Location(offset int) int {
+	return f.index + offset
 }

+ 81 - 4
feed/feed_test.go

@@ -8,12 +8,12 @@ import (
 
 var post1, _ = pub.NewPostFromObject(object.Object{
 	"type":    "Note",
-	"content": "Hello!",
+	"content": "Here from post1",
 }, nil)
 
 var post2, _ = pub.NewPostFromObject(object.Object{
 	"type":    "Video",
-	"content": "Goodbye!",
+	"content": "Here from post2",
 }, nil)
 
 func TestCreate(t *testing.T) {
@@ -26,7 +26,7 @@ func TestCreate(t *testing.T) {
 
 func TestCreateCreateAndAppend(t *testing.T) {
 	feed := CreateAndAppend([]pub.Tangible{post1})
-	shouldBePost1 := feed.Get(1)
+	shouldBePost1 := feed.Get(0)
 	if shouldBePost1 != post1 {
 		t.Fatalf("Posts differed after create centerless, is %#v but should be %#v", shouldBePost1, post1)
 	}
@@ -35,7 +35,7 @@ func TestCreateCreateAndAppend(t *testing.T) {
 			t.Fatalf("After create centerless, Get(0) should have panicked but did not")
 		}
 	}()
-	feed.Get(0)
+	feed.Get(-1)
 }
 
 func TestAppend(t *testing.T) {
@@ -63,3 +63,80 @@ func TestPrepend(t *testing.T) {
 		t.Fatalf("Prepended posts differ, is %#v but should be %#v", shouldBePost2, post2)
 	}
 }
+
+func TestMoveDown(t *testing.T) {
+	feed := CreateAndAppend([]pub.Tangible{post1, post2})
+	feed.MoveDown()
+	shouldBePost2 := feed.Current()
+	if shouldBePost2 != post2 {
+		t.Fatalf("is %#v but should be %#v", shouldBePost2, post2)
+	}
+	shouldBePost1 := feed.Get(-1)
+	if shouldBePost1 != post1 {
+		t.Fatalf("is %#v but should be %#v", shouldBePost1, post1)
+	}
+
+	defer func() {
+		if recover() == nil {
+			t.Fatalf("Get(1) should have panicked but did not")
+		}
+	}()
+	feed.Get(1)
+
+	/* Repeat everything exactly */
+	feed.MoveDown()
+	shouldBePost2 = feed.Current()
+	if shouldBePost2 != post2 {
+		t.Fatalf("is %#v but should be %#v", shouldBePost2, post2)
+	}
+	shouldBePost1 = feed.Get(-1)
+	if shouldBePost1 != post1 {
+		t.Fatalf("is %#v but should be %#v", shouldBePost1, post1)
+	}
+
+	defer func() {
+		if recover() == nil {
+			t.Fatalf("Get(1) should have panicked but did not")
+		}
+	}()
+	feed.Get(1)
+}
+
+func TestMoveUp(t *testing.T) {
+	feed := Create(post1)
+	feed.Prepend([]pub.Tangible{post2})
+	feed.MoveUp()
+	shouldBePost2 := feed.Current()
+	if shouldBePost2 != post2 {
+		t.Fatalf("is %#v but should be %#v", shouldBePost2, post2)
+	}
+	shouldBePost1 := feed.Get(1)
+	if shouldBePost1 != post1 {
+		t.Fatalf("is %#v but should be %#v", shouldBePost1, post1)
+	}
+
+	defer func() {
+		if recover() == nil {
+			t.Fatalf("Get(-1) should have panicked but did not")
+		}
+	}()
+	feed.Get(-1)
+
+	/* Repeat everything exactly */
+	feed.MoveUp()
+	shouldBePost2 = feed.Current()
+	if shouldBePost2 != post2 {
+		t.Fatalf("is %#v but should be %#v", shouldBePost2, post2)
+	}
+	shouldBePost1 = feed.Get(1)
+	if shouldBePost1 != post1 {
+		t.Fatalf("is %#v but should be %#v", shouldBePost1, post1)
+	}
+
+	defer func() {
+		if recover() == nil {
+			t.Fatalf("Get(-1) should have panicked but did not")
+		}
+	}()
+	feed.Get(-1)
+}

+ 18 - 27
ui/ui.go

@@ -39,7 +39,6 @@ const (
 
 type Page struct {
 	feed  *feed.Feed
-	index int
 
 	frontier  pub.Tangible
 	loadingUp bool
@@ -70,21 +69,21 @@ func (s *State) view() string {
 	}
 
 	var top, center, bottom string
-	for i := s.h.Current().index - s.config.Context; i <= s.h.Current().index+s.config.Context; i++ {
+	for i := -s.config.Context; i <= s.config.Context; i++ {
 		if !s.h.Current().feed.Contains(i) {
 			continue
 		}
 		var serialized string
-		if i == 0 {
+		if s.h.Current().feed.Location(i) == 0 {
 			serialized = s.h.Current().feed.Get(i).String(s.width - 4)
-		} else if i > 0 {
+		} else if s.h.Current().feed.Location(i) > 0 {
 			serialized = "→ " + ansi.Indent(s.h.Current().feed.Get(i).Preview(s.width-8), "  ", false)
 		} else {
 			serialized = s.h.Current().feed.Get(i).Preview(s.width - 4)
 		}
-		if i == s.h.Current().index {
+		if i == 0 {
 			center = ansi.Indent(serialized, "┃ ", true)
-		} else if i < s.h.Current().index {
+		} else if i < 0 {
 			if top != "" {
 				top += "\n"
 			}
@@ -206,7 +205,7 @@ func (s *State) Update(input byte) {
 			if err != nil {
 				panic("buffer had a non-number while in selection mode")
 			}
-			link, mediaType, present := s.h.Current().feed.Get(s.h.Current().index).SelectLink(number)
+			link, mediaType, present := s.h.Current().feed.Current().SelectLink(number)
 			if !present {
 				s.buffer = ""
 				s.mode = normal
@@ -230,27 +229,21 @@ func (s *State) Update(input byte) {
 	switch input {
 	// TODO: make feed stateful so all this logic is nicer. Functions will be MoveUp, MoveDown, MoveToCenter
 	case 'k': // up
-		if s.h.Current().feed.Contains(s.h.Current().index - 1) {
-			s.h.Current().index -= 1
-		}
+		s.h.Current().feed.MoveUp()
 		s.loadSurroundings()
 	case 'j': // down
-		if s.h.Current().feed.Contains(s.h.Current().index + 1) {
-			s.h.Current().index += 1
-		}
+		s.h.Current().feed.MoveDown()
 		s.loadSurroundings()
 	case 'g': // return to OP
-		if s.h.Current().feed.Contains(0) {
-			s.h.Current().index = 0
-		}
+		s.h.Current().feed.MoveToCenter()
 	case 'h': // back in history
 		s.h.Back()
 	case 'l': // forward in history
 		s.h.Forward()
 	case ' ': // select
-		s.switchTo(s.h.Current().feed.Get(s.h.Current().index))
+		s.switchTo(s.h.Current().feed.Current())
 	case 'c': // get creator of post
-		unwrapped := s.h.Current().feed.Get(s.h.Current().index)
+		unwrapped := s.h.Current().feed.Current()
 		if activity, ok := unwrapped.(*pub.Activity); ok {
 			unwrapped = activity.Target()
 		}
@@ -259,7 +252,7 @@ func (s *State) Update(input byte) {
 			s.switchTo(creators)
 		}
 	case 'r': // get recipient of post
-		unwrapped := s.h.Current().feed.Get(s.h.Current().index)
+		unwrapped := s.h.Current().feed.Current()
 		if activity, ok := unwrapped.(*pub.Activity); ok {
 			unwrapped = activity.Target()
 		}
@@ -268,12 +261,12 @@ func (s *State) Update(input byte) {
 			s.switchTo(recipients)
 		}
 	case 'a': // get actor of activity
-		if activity, ok := s.h.Current().feed.Get(s.h.Current().index).(*pub.Activity); ok {
+		if activity, ok := s.h.Current().feed.Current().(*pub.Activity); ok {
 			actor := activity.Actor()
 			s.switchTo(actor)
 		}
 	case 'o':
-		unwrapped := s.h.Current().feed.Get(s.h.Current().index)
+		unwrapped := s.h.Current().feed.Current()
 		if activity, ok := unwrapped.(*pub.Activity); ok {
 			unwrapped = activity.Target()
 		}
@@ -283,13 +276,13 @@ func (s *State) Update(input byte) {
 			}
 		}
 	case 'p':
-		if actor, ok := s.h.Current().feed.Get(s.h.Current().index).(*pub.Actor); ok {
+		if actor, ok := s.h.Current().feed.Current().(*pub.Actor); ok {
 			if link, mediaType, present := actor.ProfilePic(); present {
 				s.openExternally(link, mediaType)
 			}
 		}
 	case 'b':
-		if actor, ok := s.h.Current().feed.Get(s.h.Current().index).(*pub.Actor); ok {
+		if actor, ok := s.h.Current().feed.Current().(*pub.Actor); ok {
 			if link, mediaType, present := actor.Banner(); present {
 				s.openExternally(link, mediaType)
 			}
@@ -313,7 +306,6 @@ func (s *State) switchTo(item any) {
 		} else {
 			s.h.Add(&Page{
 				feed: feed.CreateAndAppend(narrowed),
-				index: 1,
 			})
 		}
 	case pub.Tangible:
@@ -331,7 +323,6 @@ func (s *State) switchTo(item any) {
 			basepoint: newBasepoint,
 			children: nextCollection,
 			feed: feed.CreateAndAppend(children),
-			index: 1,
 		})
 	default:
 		panic(fmt.Sprintf("unrecognized non-Tangible non-Container: %T", item))
@@ -355,7 +346,7 @@ func (s *State) SetWidthHeight(width int, height int) {
 func (s *State) loadSurroundings() {
 	page := s.h.Current()
 	context := s.config.Context
-	if !page.loadingUp && !page.feed.Contains(page.index-context) && page.frontier != nil {
+	if !page.loadingUp && !page.feed.Contains(-context) && page.frontier != nil {
 		page.loadingUp = true
 		go func() {
 			parents, newFrontier := page.frontier.Parents(uint(context))
@@ -367,7 +358,7 @@ func (s *State) loadSurroundings() {
 			s.m.Unlock()
 		}()
 	}
-	if !page.loadingDown && !page.feed.Contains(page.index+context) && page.children != nil {
+	if !page.loadingDown && !page.feed.Contains(context) && page.children != nil {
 		page.loadingDown = true
 		go func() {
 			// TODO: need to do a new renaming, maybe upperFrontier, lowerFrontier