93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// Package messaging owns bounded, session-only conversation history. Application
|
|
// adapters supply copied messages; the reader owns input, output and security.
|
|
package messaging
|
|
|
|
import "strings"
|
|
|
|
const HistorySize = 9
|
|
const maxProviders = 32
|
|
|
|
// Message contains no native objects. Provider is the unique accessibility bus
|
|
// name, so restarting an application cannot expose its previous instance's chat.
|
|
type Message struct {
|
|
Provider, Conversation, Window, RoomName, Text string
|
|
Typing bool
|
|
}
|
|
|
|
type providerHistory struct {
|
|
provider string
|
|
messages []Message
|
|
}
|
|
|
|
// History is owned by the reader event loop. Focus changes retain it; window
|
|
// destruction retires that window's messages, and screen lock clears it all.
|
|
type History struct{ providers []providerHistory }
|
|
|
|
func (history *History) Add(message Message) bool {
|
|
message.Text = strings.Trim(message.Text, "\n")
|
|
if message.Typing || strings.TrimSpace(message.Text) == "" || message.Provider == "" || message.Conversation == "" || message.Window == "" {
|
|
return false
|
|
}
|
|
characters := []rune(message.Text)
|
|
message.Text = string(characters[:min(len(characters), 4096)])
|
|
index := -1
|
|
for i := range history.providers {
|
|
if history.providers[i].provider == message.Provider {
|
|
index = i
|
|
break
|
|
}
|
|
}
|
|
if index < 0 {
|
|
if len(history.providers) == maxProviders {
|
|
history.providers = history.providers[1:]
|
|
}
|
|
history.providers = append(history.providers, providerHistory{provider: message.Provider})
|
|
index = len(history.providers) - 1
|
|
}
|
|
provider := &history.providers[index]
|
|
if len(provider.messages) == HistorySize {
|
|
provider.messages = provider.messages[1:]
|
|
}
|
|
provider.messages = append(provider.messages, message)
|
|
return true
|
|
}
|
|
|
|
// Previous uses zero for the newest message, matching Cthulhu+F1.
|
|
func (history *History) Previous(provider string, index int) (Message, bool) {
|
|
if index < 0 || index >= HistorySize {
|
|
return Message{}, false
|
|
}
|
|
for _, entry := range history.providers {
|
|
if entry.provider == provider && index < len(entry.messages) {
|
|
return entry.messages[len(entry.messages)-1-index], true
|
|
}
|
|
}
|
|
return Message{}, false
|
|
}
|
|
|
|
func (history *History) Retire(provider, path string) {
|
|
for i := len(history.providers) - 1; i >= 0; i-- {
|
|
entry := &history.providers[i]
|
|
if entry.provider != provider {
|
|
continue
|
|
}
|
|
messages := entry.messages[:0]
|
|
for _, message := range entry.messages {
|
|
if message.Window != path && message.Conversation != path {
|
|
messages = append(messages, message)
|
|
}
|
|
}
|
|
clear(entry.messages[len(messages):])
|
|
entry.messages = messages
|
|
if len(messages) == 0 {
|
|
history.providers = append(history.providers[:i], history.providers[i+1:]...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (history *History) Reset() { history.providers = nil }
|