diff --git a/go.mod b/go.mod index 667a85c..7ba4afb 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,13 @@ require ( github.com/Xuanwo/go-locale v1.0.0 github.com/alecthomas/chroma v0.7.3 github.com/diamondburned/cchat v0.3.7 - github.com/diamondburned/cchat-discord v0.0.0-20201020232329-a9f3804f5613 + github.com/diamondburned/cchat-discord v0.0.0-20201015062850-090259a6b4ca github.com/diamondburned/cchat-mock v0.0.0-20201014202453-b9838fab0ab0 github.com/diamondburned/gspell v0.0.0-20200830182722-77e5d27d6894 github.com/diamondburned/handy v0.0.0-20200829011954-4667e7a918f4 github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972 github.com/disintegration/imaging v1.6.2 github.com/goodsign/monday v1.0.0 - github.com/google/btree v1.0.0 github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/ianlancetaylor/cgosymbolizer v0.0.0-20200424224625-be1b05b0b279 diff --git a/go.sum b/go.sum index 8d707a8..be9a4c0 100644 --- a/go.sum +++ b/go.sum @@ -116,8 +116,6 @@ github.com/diamondburned/cchat-discord v0.0.0-20201009173316-1907986ceb08 h1:iyt github.com/diamondburned/cchat-discord v0.0.0-20201009173316-1907986ceb08/go.mod h1:BF8CJaW6rdYDGjFd2qXODS5nSu9vvW7OehgkXIB8B0M= github.com/diamondburned/cchat-discord v0.0.0-20201015062850-090259a6b4ca h1:36MnUdiunaz4hsqDO0313Nc03y59PzIPZtmEF8gUeCg= github.com/diamondburned/cchat-discord v0.0.0-20201015062850-090259a6b4ca/go.mod h1:S0PDR6aj2qE871JSy94YvwtprQJCWwkIJWzRu7S1Asc= -github.com/diamondburned/cchat-discord v0.0.0-20201020232329-a9f3804f5613 h1:yNgvF5JqsFvAddD0kHiD0trx1Er4Bjt4K8iPrp5ULGc= -github.com/diamondburned/cchat-discord v0.0.0-20201020232329-a9f3804f5613/go.mod h1:gsdyDkcELVS0PoTwUaDTyiVI69Kmyy0YWCjyL0x5DzM= github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b h1:sq0MXjJc3yAOZvuolRxOpKQNvpMLyTmsECxQqdYgF5E= github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b/go.mod h1:+bAf0m2o5qH54DmYJ/lR1HeITV53ol0JaoKyFFx3m3E= github.com/diamondburned/cchat-mock v0.0.0-20201004204741-b841407af381 h1:8JWNJMgoa3fL2py3gXSeC3NiAC+39EZp+JmvaoDBTUU= @@ -355,7 +353,6 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3 h1:qDJKu1y/1SjhWac4BQZjLljqvqiWUhjmDMnonmVGDAU= golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/ui/messages/container/container.go b/internal/ui/messages/container/container.go index f8c796d..b307c7d 100644 --- a/internal/ui/messages/container/container.go +++ b/internal/ui/messages/container/container.go @@ -37,10 +37,10 @@ type Container interface { cchat.MessagesContainer // Thread-unsafe methods. - - CreateMessageUnsafe(cchat.MessageCreate) int + CreateMessageUnsafe(cchat.MessageCreate) UpdateMessageUnsafe(cchat.MessageUpdate) DeleteMessageUnsafe(cchat.MessageDelete) + PrependMessageUnsafe(cchat.MessageCreate) // FirstMessage returns the first message in the buffer. Nil is returned if // there's nothing. @@ -66,7 +66,7 @@ type Controller interface { Bottomed() bool // AuthorEvent is called on message create/update. This is used to update // the typer state. - OnAuthorEvent(a cchat.Author) + AuthorEvent(a cchat.Author) } // Constructor is an interface for making custom message implementations which @@ -85,6 +85,12 @@ type GridContainer struct { Controller } +// gridMessage w/ required internals +type gridMessage struct { + GridMessage + presend message.PresendContainer // this shouldn't be here but i'm lazy +} + var _ Container = (*GridContainer)(nil) func NewGridContainer(constr Constructor, ctrl Controller) *GridContainer { @@ -96,18 +102,28 @@ func NewGridContainer(constr Constructor, ctrl Controller) *GridContainer { // CreateMessageUnsafe inserts a message as well as cleaning up the backlog if // the user is scrolled to the bottom. -func (c *GridContainer) CreateMessageUnsafe(msg cchat.MessageCreate) int { +func (c *GridContainer) CreateMessageUnsafe(msg cchat.MessageCreate) { // Insert the message first. - ix := c.GridStore.CreateMessageUnsafe(msg) + c.GridStore.CreateMessageUnsafe(msg) // Determine if the user is scrolled to the bottom for cleaning up. - if c.Bottomed() { - // Clean up the backlog. The function allows a negative n, which would - // be a no-op. - c.PopEarliestMessages(c.MessagesLen() - BacklogLimit) + if !c.Bottomed() { + return } - return ix + // Clean up the backlog. + if clean := len(c.messages) - BacklogLimit; clean > 0 { + // Remove them from the map and the container. + for _, id := range c.messageIDs[:clean] { + delete(c.messages, id) + // We can gradually pop the first item off here, as we're removing + // from 0th, and items are being shifted backwards. + c.Grid.RemoveRow(0) + } + + // Cut the message IDs away by shifting the slice. + c.messageIDs = append(c.messageIDs[:0], c.messageIDs[clean:]...) + } } func (c *GridContainer) CreateMessage(msg cchat.MessageCreate) { @@ -121,3 +137,7 @@ func (c *GridContainer) UpdateMessage(msg cchat.MessageUpdate) { func (c *GridContainer) DeleteMessage(msg cchat.MessageDelete) { gts.ExecAsync(func() { c.DeleteMessageUnsafe(msg) }) } + +func (c *GridContainer) PrependMessage(msg cchat.MessageCreate) { + gts.ExecAsync(func() { c.PrependMessageUnsafe(msg) }) +} diff --git a/internal/ui/messages/container/cozy/cozy.go b/internal/ui/messages/container/cozy/cozy.go index 11cace0..de82a6b 100644 --- a/internal/ui/messages/container/cozy/cozy.go +++ b/internal/ui/messages/container/cozy/cozy.go @@ -129,43 +129,14 @@ func (c *Container) CreateMessage(msg cchat.MessageCreate) { gts.ExecAsync(func() { // Create the message in the parent's handler. This handler will also // wipe old messages. - ix := c.GridContainer.CreateMessageUnsafe(msg) + c.GridContainer.CreateMessageUnsafe(msg) - // We need to do certain checks to messages that are prepended to the - // top of the buffer. This was originally in the now-deprecated - // PrependMessage function. - if ix == 0 { - // See if we need to uncollapse the second message. - if sec := c.NthMessage(1); sec != nil { - // If the author isn't the same, then ignore. - if sec.AuthorID() != msg.Author().ID() { - return - } - - // The author is the same; collapse. - c.compact(sec) - } - - return - } - - // Should we collapse the last message? Yes if the current message's - // author is the same as the last author. + // Should we collapse this message? Yes, if the current message's author + // is the same as the last author. if c.lastMessageIsAuthor(msg.Author().ID(), 1) { c.compact(c.GridContainer.LastMessage()) } - // See if we need to collapse the second message. - if sec := c.NthMessage(1); sec != nil { - // If the author isn't the same, then ignore. - if sec.AuthorID() != msg.Author().ID() { - return - } - - // The author is the same; collapse. - c.compact(sec) - } - // Did the handler wipe old messages? It will only do so if the user is // scrolled to the bottom. if !c.Bottomed() { @@ -240,6 +211,23 @@ func (c *Container) uncompact(msg container.GridMessage) { c.GridStore.SwapMessage(full) } +func (c *Container) PrependMessage(msg cchat.MessageCreate) { + gts.ExecAsync(func() { + c.GridContainer.PrependMessageUnsafe(msg) + + // See if we need to uncollapse the second message. + if sec := c.NthMessage(1); sec != nil { + // If the author isn't the same, then ignore. + if sec.AuthorID() != msg.Author().ID() { + return + } + + // The author is the same; collapse. + c.compact(sec) + } + }) +} + func (c *Container) compact(msg container.GridMessage) { // Exit if the message is already collapsed. if collapse, ok := msg.(Collapsible); !ok || collapse.Collapsed() { diff --git a/internal/ui/messages/container/grid.go b/internal/ui/messages/container/grid.go index 0fcc668..3c69951 100644 --- a/internal/ui/messages/container/grid.go +++ b/internal/ui/messages/container/grid.go @@ -1,6 +1,8 @@ package container import ( + "fmt" + "github.com/diamondburned/cchat" "github.com/diamondburned/cchat-gtk/internal/log" "github.com/diamondburned/cchat-gtk/internal/ui/messages/input" @@ -15,9 +17,8 @@ type GridStore struct { Construct Constructor Controller Controller - store *messageStore - // messages map[string]*gridMessage - // messageIDs []string // ids or nonces + messages map[string]*gridMessage + messageIDs []string // ids or nonces } func NewGridStore(constr Constructor, ctrl Controller) *GridStore { @@ -34,33 +35,30 @@ func NewGridStore(constr Constructor, ctrl Controller) *GridStore { Grid: grid, Construct: constr, Controller: ctrl, - store: newMessageStore(), + messages: map[string]*gridMessage{}, } } func (c *GridStore) MessagesLen() int { - return c.store.Len() + return len(c.messages) } func (c *GridStore) attachGrid(row int, widgets []gtk.IWidget) { - // // Cover a special case with attaching to the 0th row. - // switch row { - // case 0: - // c.Grid.InsertRow(0) - // case c.MessagesLen() - 1: - // row++ // ensure this doesn't try to write to the last message. - // c.Grid.InsertRow(row) - // } - - c.Grid.InsertRow(row) - - log.Println("Inserted row", row, "; length is", c.MessagesLen()) - for i, w := range widgets { c.Grid.Attach(w, i, row, 1, 1) } } +// findIndex searches backwards for idnonce. +func (c *GridStore) findIndex(idnonce string) int { + for i := len(c.messageIDs) - 1; i >= 0; i-- { + if c.messageIDs[i] == idnonce { + return i + } + } + return -1 +} + type CoordinateTranslator interface { TranslateCoordinates(dest gtk.IWidget, srcX int, srcY int) (destX int, destY int, e error) } @@ -68,11 +66,12 @@ type CoordinateTranslator interface { var _ CoordinateTranslator = (*gtk.Widget)(nil) func (c *GridStore) TranslateCoordinates(parent gtk.IWidget, msg GridMessage) (y int) { - m := c.store.Message(msg.ID(), "") - if m == nil { + i := c.findIndex(msg.ID()) + if i < 0 { return 0 } + m, _ := c.messages[c.messageIDs[i]] w, _ := m.Focusable().(CoordinateTranslator) // x is not needed. @@ -93,18 +92,18 @@ func (c *GridStore) TranslateCoordinates(parent gtk.IWidget, msg GridMessage) (y // // TODO: combine compact and full so they share the same attach method. func (c *GridStore) SwapMessage(msg GridMessage) bool { + // Get the current message's index. + var ix = c.findIndex(msg.ID()) + if ix == -1 { + return false + } + // Wrap msg inside a *gridMessage if it's not already. mg, ok := msg.(*gridMessage) if !ok { mg = &gridMessage{GridMessage: msg} } - // Get the current message's index. - var ix = c.store.SwapMessage(mg) - if ix == -1 { - return false - } - // Add a row at index. The actual row we want to delete will be shifted // downwards. c.Grid.InsertRow(ix) @@ -112,6 +111,9 @@ func (c *GridStore) SwapMessage(msg GridMessage) bool { // Let the new message be attached on top of the to-be-replaced message. c.attachGrid(ix, mg.Attach()) + // Set the message into the map. + c.messages[mg.ID()] = mg + // Delete the to-be-replaced message, which we have shifted downwards // earlier, so we add 1. c.Grid.RemoveRow(ix + 1) @@ -121,51 +123,121 @@ func (c *GridStore) SwapMessage(msg GridMessage) bool { // Before returns the message before the given ID, or nil if none. func (c *GridStore) Before(id string) GridMessage { - return c.store.MessageBefore(id) + return c.getOffsetted(id, -1) } // After returns the message after the given ID, or nil if none. func (c *GridStore) After(id string) GridMessage { - return c.store.MessageAfter(id) + return c.getOffsetted(id, 1) +} + +func (c *GridStore) getOffsetted(id string, offset int) GridMessage { + // Get the current index. + var ix = c.findIndex(id) + if ix == -1 { + return nil + } + ix += offset + + if ix < 0 || ix >= len(c.messages) { + return nil + } + + return c.messages[c.messageIDs[ix]].GridMessage } // LatestMessageFrom returns the latest message with the given user ID. This is // used for the input prompt. func (c *GridStore) LatestMessageFrom(userID string) (msgID string, ok bool) { - msg := c.store.LastMessageFrom(userID) + // FindMessage already looks from the latest messages. + var msg = c.FindMessage(func(msg GridMessage) bool { + return msg.AuthorID() == userID + }) + if msg == nil { - // "Backwards-compatibility is repeating the mistakes of yesterday, - // today." return "", false } + return msg.ID(), true } // FindMessage iterates backwards and returns the message if isMessage() returns // true on that message. func (c *GridStore) FindMessage(isMessage func(msg GridMessage) bool) GridMessage { - return c.store.FindMessage(isMessage) + for i := len(c.messageIDs) - 1; i >= 0; i-- { + msg := c.messages[c.messageIDs[i]] + // Ignore sending messages. + if msg.presend != nil { + continue + } + // Check. + if msg := msg.GridMessage; isMessage(msg) { + return msg + } + } + return nil } // NthMessage returns the nth message. func (c *GridStore) NthMessage(n int) GridMessage { - return c.store.NthMessage(n).unwrap() + if len(c.messageIDs) > 0 && n >= 0 && n < len(c.messageIDs) { + return c.messages[c.messageIDs[n]].GridMessage + } + return nil } // FirstMessage returns the first message. func (c *GridStore) FirstMessage() GridMessage { - return c.store.FirstMessage().unwrap() + return c.NthMessage(0) } // LastMessage returns the latest message. func (c *GridStore) LastMessage() GridMessage { - return c.store.LastMessage().unwrap() + return c.NthMessage(c.MessagesLen() - 1) } // Message finds the message state in the container. It is not thread-safe. This // exists for backwards compatibility. func (c *GridStore) Message(msgID cchat.ID, nonce string) GridMessage { - return c.store.Message(msgID, nonce).unwrap() + if m := c.message(msgID, nonce); m != nil { + return m.GridMessage + } + return nil +} + +func (c *GridStore) message(msgID cchat.ID, nonce string) *gridMessage { + // Search using the ID first. + m, ok := c.messages[msgID] + if ok { + return m + } + + // Is this an existing message? + if nonce != "" { + // Things in this map are guaranteed to have presend != nil. + m, ok := c.messages[nonce] + if ok { + // Replace the nonce key with ID. + delete(c.messages, nonce) + c.messages[msgID] = m + + // Set the right ID. + m.presend.SetDone(msgID) + // Destroy the presend struct. + m.presend = nil + + // Replace the nonce inside the ID slice with the actual ID. + if ix := c.findIndex(nonce); ix > -1 { + c.messageIDs[ix] = msgID + } else { + log.Error(fmt.Errorf("Missed ID %s in slice index %d", msgID, ix)) + } + + return m + } + } + + return nil } // AddPresendMessage inserts an input.PresendMessage into the container and @@ -178,23 +250,38 @@ func (c *GridStore) AddPresendMessage(msg input.PresendMessage) PresendGridMessa presend: presend, } - // Crash and burn if -1 is returned. - ix := c.store.InsertMessage(msgc) - if ix == -1 { - panic("BUG: -1 returned from store.InsertMessage") - } - // Set the message into the grid. - c.attachGrid(ix, msgc.Attach()) + c.attachGrid(c.MessagesLen(), msgc.Attach()) + // Append the NONCE. + c.messageIDs = append(c.messageIDs, msgc.Nonce()) + // Set the NONCE into the message map. + c.messages[msgc.Nonce()] = msgc return presend } -// CreateMessageUnsafe adds msg into the message view. It returns -1 if the -// message was "upserted," that is if it's updated instead of inserted. -func (c *GridStore) CreateMessageUnsafe(msg cchat.MessageCreate) int { +func (c *GridStore) PrependMessageUnsafe(msg cchat.MessageCreate) { + msgc := &gridMessage{ + GridMessage: c.Construct.NewMessage(msg), + } + + c.Grid.InsertRow(0) + c.attachGrid(0, msgc.Attach()) + + // Prepend the message ID. + c.messageIDs = append(c.messageIDs, "") + copy(c.messageIDs[1:], c.messageIDs) + c.messageIDs[0] = msgc.ID() + + // Set the message into the map. + c.messages[msgc.ID()] = msgc + + c.Controller.BindMenu(msgc) +} + +func (c *GridStore) CreateMessageUnsafe(msg cchat.MessageCreate) { // Call the event handler last. - defer c.Controller.OnAuthorEvent(msg.Author()) + defer c.Controller.AuthorEvent(msg.Author()) // Attempt to update before insertion (aka upsert). if msgc := c.Message(msg.ID(), msg.Nonce()); msgc != nil { @@ -203,29 +290,24 @@ func (c *GridStore) CreateMessageUnsafe(msg cchat.MessageCreate) int { msgc.UpdateTimestamp(msg.Time()) c.Controller.BindMenu(msgc) - return -1 + return } msgc := &gridMessage{ GridMessage: c.Construct.NewMessage(msg), } - // Crash and burn if -1 is returned. - ix := c.store.InsertMessage(msgc) - if ix == -1 { - panic("BUG: -1 returned from store.InsertMessage") - } + // Copy from PresendMessage. + c.attachGrid(c.MessagesLen(), msgc.Attach()) + c.messageIDs = append(c.messageIDs, msgc.ID()) + c.messages[msgc.ID()] = msgc - // Set the message into the grid. - c.attachGrid(ix, msgc.Attach()) c.Controller.BindMenu(msgc) - - return ix } func (c *GridStore) UpdateMessageUnsafe(msg cchat.MessageUpdate) { // Call the event handler last. - defer c.Controller.OnAuthorEvent(msg.Author()) + defer c.Controller.AuthorEvent(msg.Author()) if msgc := c.Message(msg.ID(), ""); msgc != nil { if author := msg.Author(); author != nil { @@ -240,31 +322,26 @@ func (c *GridStore) UpdateMessageUnsafe(msg cchat.MessageUpdate) { } func (c *GridStore) DeleteMessageUnsafe(msg cchat.MessageDelete) { - c.store.DeleteMessage(msg.ID()) + c.PopMessage(msg.ID()) } // PopMessage deletes a message off of the list and return the deleted message. -func (c *GridStore) PopMessage(id string) GridMessage { - msg, ix := c.store.PopMessage(id) - if msg == nil { +func (c *GridStore) PopMessage(id string) (msg GridMessage) { + // Search for the index. + var ix = c.findIndex(id) + if ix < 0 { return nil } + // Grab the message before deleting. + msg = c.messages[id] + // Remove off of the Gtk grid. c.Grid.RemoveRow(ix) + // Pop off the slice. + c.messageIDs = append(c.messageIDs[:ix], c.messageIDs[ix+1:]...) + // Delete off the map. + delete(c.messages, id) - return msg.GridMessage -} - -func (c *GridStore) PopEarliestMessages(n int) { - poppedIxs := c.store.PopEarliestMessages(n) - if poppedIxs == 0 { - return - } - // Get the count of messages after deletion. We can then gradually decrement - // poppedN to get the deleted message indices. - for poppedIxs > 0 { - c.Grid.RemoveRow(poppedIxs) - poppedIxs-- - } + return } diff --git a/internal/ui/messages/container/gridmessage.go b/internal/ui/messages/container/gridmessage.go deleted file mode 100644 index 3df57f2..0000000 --- a/internal/ui/messages/container/gridmessage.go +++ /dev/null @@ -1,35 +0,0 @@ -package container - -import ( - "github.com/diamondburned/cchat-gtk/internal/ui/messages/message" - "github.com/google/btree" -) - -// gridMessage w/ required internals -type gridMessage struct { - GridMessage - presend message.PresendContainer // this shouldn't be here but i'm lazy -} - -// Less compares the time while accounting for equal time with different IDs. -func (g *gridMessage) Less(than btree.Item) bool { - thanMessage := than.(*gridMessage) - - // Time must never match if the IDs don't. - if thanMessage.Time().Equal(g.Time()) { - if thanMessage.ID() != g.ID() { - // Always return less = true because this shouldn't be equal. - return true - } - } - - return g.Time().Before(thanMessage.Time()) -} - -// unwrap returns nil if g is nil. Otherwise, it unwraps the gridMessage. -func (g *gridMessage) unwrap() GridMessage { - if g == nil { - return nil - } - return g.GridMessage -} diff --git a/internal/ui/messages/container/store.go b/internal/ui/messages/container/store.go deleted file mode 100644 index de7e92b..0000000 --- a/internal/ui/messages/container/store.go +++ /dev/null @@ -1,242 +0,0 @@ -package container - -import ( - "github.com/diamondburned/cchat" - "github.com/google/btree" -) - -// messageStore implements various data structures for optimized message get and -// insert. -type messageStore struct { - msgTree btree.BTree - messageIDs map[string]*gridMessage - messageNonces map[string]*gridMessage -} - -func newMessageStore() *messageStore { - return &messageStore{ - msgTree: *btree.New(2), - messageIDs: make(map[string]*gridMessage, 100), - messageNonces: make(map[string]*gridMessage, 5), - } -} - -func (ms *messageStore) Len() int { - return ms.msgTree.Len() -} - -// InsertMessage inserts the message into the store and return the new index. -func (ms *messageStore) InsertMessage(msg *gridMessage) int { - return ms.replaceMessage(msg, false) -} - -// SwapMessage overrides the old message with the same ID with the given one. It -// returns an index if the message is replaced, or -1 if the message is not. -func (ms *messageStore) SwapMessage(msg *gridMessage) int { - return ms.replaceMessage(msg, true) -} - -func (ms *messageStore) replaceMessage(msg *gridMessage, replaceOnly bool) int { - var ix = -1 - - // Guarantee that no new messages are added. - if replaced := ms.msgTree.ReplaceOrInsert(msg); replaced == nil && replaceOnly { - // Nil is returned, meaning a new message is added. This is bad. - ms.msgTree.Delete(msg) - return ix - } - - var id = msg.ID() - if id != "" { - ms.messageIDs[id] = msg - delete(ms.messageNonces, msg.Nonce()) // superfluous guarantee - } else { - // Assume nonce is non-empty. Probably not a good idea. - ms.messageNonces[msg.Nonce()] = msg - } - - insertAt := ms.msgTree.Len() - 1 - - ms.msgTree.Descend(func(item btree.Item) bool { - if id == item.(*gridMessage).ID() { - ix = insertAt - return false // break - } - insertAt-- - return true - }) - - return ix -} - -func (ms *messageStore) MessageBefore(id cchat.ID) GridMessage { - return ms.getOffsetted(id, true) -} - -func (ms *messageStore) MessageAfter(id cchat.ID) GridMessage { - return ms.getOffsetted(id, false) -} - -// getOffsetted returns the unwrapped message. -func (ms *messageStore) getOffsetted(id cchat.ID, before bool) GridMessage { - var last, found *gridMessage - var next bool - - // We need to ascend, as next and before implies ascending order from 0 to - // last. - ms.msgTree.Ascend(func(item btree.Item) bool { - message := item.(*gridMessage) - if next { - found = message - return false // break - } - if message.ID() == id { - if before { - found = last - return false // break - } else { - next = true - return true - } - } - last = message - return true - }) - - if found == nil { - return nil - } - - return found.GridMessage -} - -// FirstMessage returns the earliest message. -func (ms *messageStore) FirstMessage() *gridMessage { - return ms.msgTree.Min().(*gridMessage) -} - -// LastMessage returns the latest message. -func (ms *messageStore) LastMessage() *gridMessage { - return ms.msgTree.Max().(*gridMessage) -} - -// NthMessage returns the nth message ordered from earliest to latest. It is -// fairly slow. -func (ms *messageStore) NthMessage(n int) (message *gridMessage) { - insertAt := ms.msgTree.Len() - 1 - - ms.msgTree.Descend(func(item btree.Item) bool { - if n == insertAt { - message = item.(*gridMessage) - return false // break - } - insertAt-- - return true - }) - - return -} - -// LastMessageFrom returns the latest message with the given user ID. This is -// used for the input prompt. -func (ms *messageStore) LastMessageFrom(userID string) GridMessage { - return ms.FindMessage(func(gridMsg GridMessage) bool { - return gridMsg.AuthorID() == userID - }) -} - -// FindMessage implicitly unwraps the GridMessage before passing it into the -// handler and returning it. -func (ms *messageStore) FindMessage(isMessage func(GridMessage) bool) (found GridMessage) { - ms.msgTree.Descend(func(item btree.Item) bool { - message := item.(*gridMessage) - // Ignore sending messages. - if message.presend != nil { - return true - } - if unwrapped := message.GridMessage; isMessage(unwrapped) { - found = unwrapped - return false - } - return true - }) - return -} - -func (ms *messageStore) get(id, nonce string) *gridMessage { - if id != "" { - m, ok := ms.messageIDs[id] - if ok { - return m - } - } - - m, ok := ms.messageNonces[nonce] - if ok { - return m - } - - return nil -} - -func (ms *messageStore) Message(msgID cchat.ID, nonce string) *gridMessage { - message := ms.get(msgID, nonce) - - // If the message was obtained from a nonce, then try to move it off. - if nonce != "" && message != nil { - // Move the message from nonce state to ID. - delete(ms.messageNonces, nonce) - ms.messageIDs[msgID] = message - - // Set the right ID. - message.presend.SetDone(msgID) - // Destroy the presend struct. - message.presend = nil - } - - return message -} - -func (ms *messageStore) DeleteMessage(msgID cchat.ID) { - m, ok := ms.messageIDs[msgID] - if ok { - ms.msgTree.Delete(m) - delete(ms.messageIDs, msgID) - } -} - -func (ms *messageStore) PopMessage(id cchat.ID) (popped *gridMessage, ix int) { - ix = ms.msgTree.Len() - 1 - - ms.msgTree.Descend(func(item btree.Item) bool { - if gridMsg := item.(*gridMessage); id == gridMsg.ID() { - popped = gridMsg - - // Delete off of the state. - ms.msgTree.Delete(item) - delete(ms.messageIDs, id) - delete(ms.messageNonces, popped.Nonce()) // superfluous - - return false // break - } - ix-- - return true - }) - - return -} - -// PopEarliestMessages pops the n earliest messages. n can be less than or equal -// to 0, which would be a no-op. -func (ms *messageStore) PopEarliestMessages(n int) (poppedIxs int) { - for ; n > 0 && ms.Len() > 0; n-- { - gridMsg := ms.msgTree.DeleteMin().(*gridMessage) - delete(ms.messageIDs, gridMsg.ID()) - delete(ms.messageNonces, gridMsg.Nonce()) - - // We can keep incrementing the index as we delete things. This is - // because we're deleting from 0 and up. - poppedIxs++ - } - return -} diff --git a/internal/ui/messages/message/message.go b/internal/ui/messages/message/message.go index d14ca18..b9ce639 100644 --- a/internal/ui/messages/message/message.go +++ b/internal/ui/messages/message/message.go @@ -17,7 +17,6 @@ import ( type Container interface { ID() string - Time() time.Time AuthorID() string AvatarURL() string // avatar Nonce() string diff --git a/internal/ui/messages/view.go b/internal/ui/messages/view.go index da30ad3..7f79cf5 100644 --- a/internal/ui/messages/view.go +++ b/internal/ui/messages/view.go @@ -364,8 +364,8 @@ func (v *View) AddPresendMessage(msg input.PresendMessage) func(error) { } } -// OnAuthorEvent should be called on message create/update/delete. -func (v *View) OnAuthorEvent(author cchat.Author) { +// AuthorEvent should be called on message create/update/delete. +func (v *View) AuthorEvent(author cchat.Author) { // Remove the author from the typing list if it's not nil. if author != nil { v.Typing.RemoveAuthor(author) diff --git a/internal/ui/primitives/completion/completer.go b/internal/ui/primitives/completion/completer.go index 55c3ed2..6a34d5e 100644 --- a/internal/ui/primitives/completion/completer.go +++ b/internal/ui/primitives/completion/completer.go @@ -2,6 +2,7 @@ package completion import ( "fmt" + "log" "github.com/diamondburned/cchat" "github.com/diamondburned/cchat-gtk/internal/gts/httputil" @@ -137,16 +138,20 @@ func (c *Completer) onChange() { t, v, blank := State(c.Buffer) c.cursor = v + log.Println("STATE:", t, v, blank) + // If the cursor is on a blank character, then we should not // autocomplete anything, so we set the states to nil. if blank { c.words = nil c.index = -1 c.Popdown() + log.Println("RESET INDEX TO -1") return } c.words, c.index = c.Splitter(t, v) + log.Println("INDEX:", c.index) c.complete() }