parent
d9205b3452
commit
7b4134e20d
3
go.mod
3
go.mod
|
@ -10,14 +10,13 @@ require (
|
||||||
github.com/Xuanwo/go-locale v1.0.0
|
github.com/Xuanwo/go-locale v1.0.0
|
||||||
github.com/alecthomas/chroma v0.7.3
|
github.com/alecthomas/chroma v0.7.3
|
||||||
github.com/diamondburned/cchat v0.3.7
|
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/cchat-mock v0.0.0-20201014202453-b9838fab0ab0
|
||||||
github.com/diamondburned/gspell v0.0.0-20200830182722-77e5d27d6894
|
github.com/diamondburned/gspell v0.0.0-20200830182722-77e5d27d6894
|
||||||
github.com/diamondburned/handy v0.0.0-20200829011954-4667e7a918f4
|
github.com/diamondburned/handy v0.0.0-20200829011954-4667e7a918f4
|
||||||
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
|
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/goodsign/monday v1.0.0
|
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/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
||||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
||||||
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20200424224625-be1b05b0b279
|
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20200424224625-be1b05b0b279
|
||||||
|
|
3
go.sum
3
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-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 h1:36MnUdiunaz4hsqDO0313Nc03y59PzIPZtmEF8gUeCg=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201015062850-090259a6b4ca/go.mod h1:S0PDR6aj2qE871JSy94YvwtprQJCWwkIJWzRu7S1Asc=
|
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 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-20200709231652-ad222ce5a74b/go.mod h1:+bAf0m2o5qH54DmYJ/lR1HeITV53ol0JaoKyFFx3m3E=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201004204741-b841407af381 h1:8JWNJMgoa3fL2py3gXSeC3NiAC+39EZp+JmvaoDBTUU=
|
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-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 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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/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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
|
@ -37,10 +37,10 @@ type Container interface {
|
||||||
cchat.MessagesContainer
|
cchat.MessagesContainer
|
||||||
|
|
||||||
// Thread-unsafe methods.
|
// Thread-unsafe methods.
|
||||||
|
CreateMessageUnsafe(cchat.MessageCreate)
|
||||||
CreateMessageUnsafe(cchat.MessageCreate) int
|
|
||||||
UpdateMessageUnsafe(cchat.MessageUpdate)
|
UpdateMessageUnsafe(cchat.MessageUpdate)
|
||||||
DeleteMessageUnsafe(cchat.MessageDelete)
|
DeleteMessageUnsafe(cchat.MessageDelete)
|
||||||
|
PrependMessageUnsafe(cchat.MessageCreate)
|
||||||
|
|
||||||
// FirstMessage returns the first message in the buffer. Nil is returned if
|
// FirstMessage returns the first message in the buffer. Nil is returned if
|
||||||
// there's nothing.
|
// there's nothing.
|
||||||
|
@ -66,7 +66,7 @@ type Controller interface {
|
||||||
Bottomed() bool
|
Bottomed() bool
|
||||||
// AuthorEvent is called on message create/update. This is used to update
|
// AuthorEvent is called on message create/update. This is used to update
|
||||||
// the typer state.
|
// the typer state.
|
||||||
OnAuthorEvent(a cchat.Author)
|
AuthorEvent(a cchat.Author)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor is an interface for making custom message implementations which
|
// Constructor is an interface for making custom message implementations which
|
||||||
|
@ -85,6 +85,12 @@ type GridContainer struct {
|
||||||
Controller
|
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)
|
var _ Container = (*GridContainer)(nil)
|
||||||
|
|
||||||
func NewGridContainer(constr Constructor, ctrl Controller) *GridContainer {
|
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
|
// CreateMessageUnsafe inserts a message as well as cleaning up the backlog if
|
||||||
// the user is scrolled to the bottom.
|
// 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.
|
// 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.
|
// Determine if the user is scrolled to the bottom for cleaning up.
|
||||||
if c.Bottomed() {
|
if !c.Bottomed() {
|
||||||
// Clean up the backlog. The function allows a negative n, which would
|
return
|
||||||
// be a no-op.
|
|
||||||
c.PopEarliestMessages(c.MessagesLen() - BacklogLimit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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) {
|
func (c *GridContainer) DeleteMessage(msg cchat.MessageDelete) {
|
||||||
gts.ExecAsync(func() { c.DeleteMessageUnsafe(msg) })
|
gts.ExecAsync(func() { c.DeleteMessageUnsafe(msg) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GridContainer) PrependMessage(msg cchat.MessageCreate) {
|
||||||
|
gts.ExecAsync(func() { c.PrependMessageUnsafe(msg) })
|
||||||
|
}
|
||||||
|
|
|
@ -129,43 +129,14 @@ func (c *Container) CreateMessage(msg cchat.MessageCreate) {
|
||||||
gts.ExecAsync(func() {
|
gts.ExecAsync(func() {
|
||||||
// Create the message in the parent's handler. This handler will also
|
// Create the message in the parent's handler. This handler will also
|
||||||
// wipe old messages.
|
// 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
|
// Should we collapse this message? Yes, if the current message's author
|
||||||
// top of the buffer. This was originally in the now-deprecated
|
// is the same as the last author.
|
||||||
// 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.
|
|
||||||
if c.lastMessageIsAuthor(msg.Author().ID(), 1) {
|
if c.lastMessageIsAuthor(msg.Author().ID(), 1) {
|
||||||
c.compact(c.GridContainer.LastMessage())
|
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
|
// Did the handler wipe old messages? It will only do so if the user is
|
||||||
// scrolled to the bottom.
|
// scrolled to the bottom.
|
||||||
if !c.Bottomed() {
|
if !c.Bottomed() {
|
||||||
|
@ -240,6 +211,23 @@ func (c *Container) uncompact(msg container.GridMessage) {
|
||||||
c.GridStore.SwapMessage(full)
|
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) {
|
func (c *Container) compact(msg container.GridMessage) {
|
||||||
// Exit if the message is already collapsed.
|
// Exit if the message is already collapsed.
|
||||||
if collapse, ok := msg.(Collapsible); !ok || collapse.Collapsed() {
|
if collapse, ok := msg.(Collapsible); !ok || collapse.Collapsed() {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
||||||
|
@ -15,9 +17,8 @@ type GridStore struct {
|
||||||
Construct Constructor
|
Construct Constructor
|
||||||
Controller Controller
|
Controller Controller
|
||||||
|
|
||||||
store *messageStore
|
messages map[string]*gridMessage
|
||||||
// messages map[string]*gridMessage
|
messageIDs []string // ids or nonces
|
||||||
// messageIDs []string // ids or nonces
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGridStore(constr Constructor, ctrl Controller) *GridStore {
|
func NewGridStore(constr Constructor, ctrl Controller) *GridStore {
|
||||||
|
@ -34,33 +35,30 @@ func NewGridStore(constr Constructor, ctrl Controller) *GridStore {
|
||||||
Grid: grid,
|
Grid: grid,
|
||||||
Construct: constr,
|
Construct: constr,
|
||||||
Controller: ctrl,
|
Controller: ctrl,
|
||||||
store: newMessageStore(),
|
messages: map[string]*gridMessage{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GridStore) MessagesLen() int {
|
func (c *GridStore) MessagesLen() int {
|
||||||
return c.store.Len()
|
return len(c.messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GridStore) attachGrid(row int, widgets []gtk.IWidget) {
|
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 {
|
for i, w := range widgets {
|
||||||
c.Grid.Attach(w, i, row, 1, 1)
|
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 {
|
type CoordinateTranslator interface {
|
||||||
TranslateCoordinates(dest gtk.IWidget, srcX int, srcY int) (destX int, destY int, e error)
|
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)
|
var _ CoordinateTranslator = (*gtk.Widget)(nil)
|
||||||
|
|
||||||
func (c *GridStore) TranslateCoordinates(parent gtk.IWidget, msg GridMessage) (y int) {
|
func (c *GridStore) TranslateCoordinates(parent gtk.IWidget, msg GridMessage) (y int) {
|
||||||
m := c.store.Message(msg.ID(), "")
|
i := c.findIndex(msg.ID())
|
||||||
if m == nil {
|
if i < 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m, _ := c.messages[c.messageIDs[i]]
|
||||||
w, _ := m.Focusable().(CoordinateTranslator)
|
w, _ := m.Focusable().(CoordinateTranslator)
|
||||||
|
|
||||||
// x is not needed.
|
// 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.
|
// TODO: combine compact and full so they share the same attach method.
|
||||||
func (c *GridStore) SwapMessage(msg GridMessage) bool {
|
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.
|
// Wrap msg inside a *gridMessage if it's not already.
|
||||||
mg, ok := msg.(*gridMessage)
|
mg, ok := msg.(*gridMessage)
|
||||||
if !ok {
|
if !ok {
|
||||||
mg = &gridMessage{GridMessage: msg}
|
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
|
// Add a row at index. The actual row we want to delete will be shifted
|
||||||
// downwards.
|
// downwards.
|
||||||
c.Grid.InsertRow(ix)
|
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.
|
// Let the new message be attached on top of the to-be-replaced message.
|
||||||
c.attachGrid(ix, mg.Attach())
|
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
|
// Delete the to-be-replaced message, which we have shifted downwards
|
||||||
// earlier, so we add 1.
|
// earlier, so we add 1.
|
||||||
c.Grid.RemoveRow(ix + 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.
|
// Before returns the message before the given ID, or nil if none.
|
||||||
func (c *GridStore) Before(id string) GridMessage {
|
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.
|
// After returns the message after the given ID, or nil if none.
|
||||||
func (c *GridStore) After(id string) GridMessage {
|
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
|
// LatestMessageFrom returns the latest message with the given user ID. This is
|
||||||
// used for the input prompt.
|
// used for the input prompt.
|
||||||
func (c *GridStore) LatestMessageFrom(userID string) (msgID string, ok bool) {
|
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 {
|
if msg == nil {
|
||||||
// "Backwards-compatibility is repeating the mistakes of yesterday,
|
|
||||||
// today."
|
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg.ID(), true
|
return msg.ID(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindMessage iterates backwards and returns the message if isMessage() returns
|
// FindMessage iterates backwards and returns the message if isMessage() returns
|
||||||
// true on that message.
|
// true on that message.
|
||||||
func (c *GridStore) FindMessage(isMessage func(msg GridMessage) bool) GridMessage {
|
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.
|
// NthMessage returns the nth message.
|
||||||
func (c *GridStore) NthMessage(n int) GridMessage {
|
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.
|
// FirstMessage returns the first message.
|
||||||
func (c *GridStore) FirstMessage() GridMessage {
|
func (c *GridStore) FirstMessage() GridMessage {
|
||||||
return c.store.FirstMessage().unwrap()
|
return c.NthMessage(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastMessage returns the latest message.
|
// LastMessage returns the latest message.
|
||||||
func (c *GridStore) LastMessage() GridMessage {
|
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
|
// Message finds the message state in the container. It is not thread-safe. This
|
||||||
// exists for backwards compatibility.
|
// exists for backwards compatibility.
|
||||||
func (c *GridStore) Message(msgID cchat.ID, nonce string) GridMessage {
|
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
|
// AddPresendMessage inserts an input.PresendMessage into the container and
|
||||||
|
@ -178,23 +250,38 @@ func (c *GridStore) AddPresendMessage(msg input.PresendMessage) PresendGridMessa
|
||||||
presend: presend,
|
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.
|
// 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
|
return presend
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMessageUnsafe adds msg into the message view. It returns -1 if the
|
func (c *GridStore) PrependMessageUnsafe(msg cchat.MessageCreate) {
|
||||||
// message was "upserted," that is if it's updated instead of inserted.
|
msgc := &gridMessage{
|
||||||
func (c *GridStore) CreateMessageUnsafe(msg cchat.MessageCreate) int {
|
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.
|
// Call the event handler last.
|
||||||
defer c.Controller.OnAuthorEvent(msg.Author())
|
defer c.Controller.AuthorEvent(msg.Author())
|
||||||
|
|
||||||
// Attempt to update before insertion (aka upsert).
|
// Attempt to update before insertion (aka upsert).
|
||||||
if msgc := c.Message(msg.ID(), msg.Nonce()); msgc != nil {
|
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())
|
msgc.UpdateTimestamp(msg.Time())
|
||||||
|
|
||||||
c.Controller.BindMenu(msgc)
|
c.Controller.BindMenu(msgc)
|
||||||
return -1
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msgc := &gridMessage{
|
msgc := &gridMessage{
|
||||||
GridMessage: c.Construct.NewMessage(msg),
|
GridMessage: c.Construct.NewMessage(msg),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crash and burn if -1 is returned.
|
// Copy from PresendMessage.
|
||||||
ix := c.store.InsertMessage(msgc)
|
c.attachGrid(c.MessagesLen(), msgc.Attach())
|
||||||
if ix == -1 {
|
c.messageIDs = append(c.messageIDs, msgc.ID())
|
||||||
panic("BUG: -1 returned from store.InsertMessage")
|
c.messages[msgc.ID()] = msgc
|
||||||
}
|
|
||||||
|
|
||||||
// Set the message into the grid.
|
|
||||||
c.attachGrid(ix, msgc.Attach())
|
|
||||||
c.Controller.BindMenu(msgc)
|
c.Controller.BindMenu(msgc)
|
||||||
|
|
||||||
return ix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GridStore) UpdateMessageUnsafe(msg cchat.MessageUpdate) {
|
func (c *GridStore) UpdateMessageUnsafe(msg cchat.MessageUpdate) {
|
||||||
// Call the event handler last.
|
// 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 msgc := c.Message(msg.ID(), ""); msgc != nil {
|
||||||
if author := msg.Author(); author != 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) {
|
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.
|
// PopMessage deletes a message off of the list and return the deleted message.
|
||||||
func (c *GridStore) PopMessage(id string) GridMessage {
|
func (c *GridStore) PopMessage(id string) (msg GridMessage) {
|
||||||
msg, ix := c.store.PopMessage(id)
|
// Search for the index.
|
||||||
if msg == nil {
|
var ix = c.findIndex(id)
|
||||||
|
if ix < 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grab the message before deleting.
|
||||||
|
msg = c.messages[id]
|
||||||
|
|
||||||
// Remove off of the Gtk grid.
|
// Remove off of the Gtk grid.
|
||||||
c.Grid.RemoveRow(ix)
|
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
|
return
|
||||||
}
|
|
||||||
|
|
||||||
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--
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
|
|
||||||
type Container interface {
|
type Container interface {
|
||||||
ID() string
|
ID() string
|
||||||
Time() time.Time
|
|
||||||
AuthorID() string
|
AuthorID() string
|
||||||
AvatarURL() string // avatar
|
AvatarURL() string // avatar
|
||||||
Nonce() string
|
Nonce() string
|
||||||
|
|
|
@ -364,8 +364,8 @@ func (v *View) AddPresendMessage(msg input.PresendMessage) func(error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnAuthorEvent should be called on message create/update/delete.
|
// AuthorEvent should be called on message create/update/delete.
|
||||||
func (v *View) OnAuthorEvent(author cchat.Author) {
|
func (v *View) AuthorEvent(author cchat.Author) {
|
||||||
// Remove the author from the typing list if it's not nil.
|
// Remove the author from the typing list if it's not nil.
|
||||||
if author != nil {
|
if author != nil {
|
||||||
v.Typing.RemoveAuthor(author)
|
v.Typing.RemoveAuthor(author)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package completion
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts/httputil"
|
"github.com/diamondburned/cchat-gtk/internal/gts/httputil"
|
||||||
|
@ -137,16 +138,20 @@ func (c *Completer) onChange() {
|
||||||
t, v, blank := State(c.Buffer)
|
t, v, blank := State(c.Buffer)
|
||||||
c.cursor = v
|
c.cursor = v
|
||||||
|
|
||||||
|
log.Println("STATE:", t, v, blank)
|
||||||
|
|
||||||
// If the cursor is on a blank character, then we should not
|
// If the cursor is on a blank character, then we should not
|
||||||
// autocomplete anything, so we set the states to nil.
|
// autocomplete anything, so we set the states to nil.
|
||||||
if blank {
|
if blank {
|
||||||
c.words = nil
|
c.words = nil
|
||||||
c.index = -1
|
c.index = -1
|
||||||
c.Popdown()
|
c.Popdown()
|
||||||
|
log.Println("RESET INDEX TO -1")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.words, c.index = c.Splitter(t, v)
|
c.words, c.index = c.Splitter(t, v)
|
||||||
|
log.Println("INDEX:", c.index)
|
||||||
c.complete()
|
c.complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue