Refactor UI to use interfaces everywhere
This commit is contained in:
parent
2ba2fde396
commit
eda2b575f0
@ -23,7 +23,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains the main config of gomuks.
|
// Config contains the main config of gomuks.
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
|
@ -18,104 +18,43 @@ package debug
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"maunium.net/go/tview"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Printer interface {
|
var writer io.Writer
|
||||||
Printf(text string, args ...interface{})
|
|
||||||
Print(text ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pane struct {
|
func init() {
|
||||||
*tview.TextView
|
var err error
|
||||||
Height int
|
writer, err = os.OpenFile("/tmp/gomuks-debug.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||||
Width int
|
if err != nil {
|
||||||
num int
|
writer = nil
|
||||||
}
|
|
||||||
|
|
||||||
var Default Printer
|
|
||||||
var RedirectAllExt bool
|
|
||||||
|
|
||||||
func NewPane() *Pane {
|
|
||||||
pane := tview.NewTextView()
|
|
||||||
pane.
|
|
||||||
SetScrollable(true).
|
|
||||||
SetWrap(true).
|
|
||||||
SetBorder(true).
|
|
||||||
SetTitle("Debug output")
|
|
||||||
fmt.Fprintln(pane, "[0] Debug pane initialized")
|
|
||||||
|
|
||||||
return &Pane{
|
|
||||||
TextView: pane,
|
|
||||||
Height: 35,
|
|
||||||
Width: 80,
|
|
||||||
num: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Pane) Printf(text string, args ...interface{}) {
|
|
||||||
db.WriteString(fmt.Sprintf(text, args...) + "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Pane) Print(text ...interface{}) {
|
|
||||||
db.WriteString(fmt.Sprintln(text...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Pane) WriteString(text string) {
|
|
||||||
db.num++
|
|
||||||
fmt.Fprintf(db, "[%d] %s", db.num, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaneSide int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Top PaneSide = iota
|
|
||||||
Bottom
|
|
||||||
Left
|
|
||||||
Right
|
|
||||||
)
|
|
||||||
|
|
||||||
func (db *Pane) Wrap(main tview.Primitive, side PaneSide) tview.Primitive {
|
|
||||||
rows, columns := []int{0}, []int{0}
|
|
||||||
mainRow, mainColumn, paneRow, paneColumn := 0, 0, 0, 0
|
|
||||||
switch side {
|
|
||||||
case Top:
|
|
||||||
rows = []int{db.Height, 0}
|
|
||||||
mainRow = 1
|
|
||||||
case Bottom:
|
|
||||||
rows = []int{0, db.Height}
|
|
||||||
paneRow = 1
|
|
||||||
case Left:
|
|
||||||
columns = []int{db.Width, 0}
|
|
||||||
mainColumn = 1
|
|
||||||
case Right:
|
|
||||||
columns = []int{0, db.Width}
|
|
||||||
paneColumn = 1
|
|
||||||
}
|
|
||||||
return tview.NewGrid().SetRows(rows...).SetColumns(columns...).
|
|
||||||
AddItem(main, mainRow, mainColumn, 1, 1, 1, 1, true).
|
|
||||||
AddItem(db, paneRow, paneColumn, 1, 1, 1, 1, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Printf(text string, args ...interface{}) {
|
func Printf(text string, args ...interface{}) {
|
||||||
if RedirectAllExt {
|
if writer != nil {
|
||||||
ExtPrintf(text, args...)
|
fmt.Fprintf(writer, time.Now().Format("[2006-01-02 15:04:05] "))
|
||||||
} else if Default != nil {
|
fmt.Fprintf(writer, text+"\n", args...)
|
||||||
Default.Printf(text, args...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Print(text ...interface{}) {
|
func Print(text ...interface{}) {
|
||||||
if RedirectAllExt {
|
if writer != nil {
|
||||||
ExtPrint(text...)
|
fmt.Fprintf(writer, time.Now().Format("[2006-01-02 15:04:05] "))
|
||||||
} else if Default != nil {
|
fmt.Fprintln(writer, text...)
|
||||||
Default.Print(text...)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintStack() {
|
||||||
|
if writer != nil {
|
||||||
|
data := debug.Stack()
|
||||||
|
writer.Write(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
debug/doc.go
Normal file
2
debug/doc.go
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Package debug contains utilities to log debug messages and display panics nicely.
|
||||||
|
package debug
|
37
gomuks.go
37
gomuks.go
@ -23,10 +23,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix"
|
"maunium.net/go/gomuks/matrix"
|
||||||
"maunium.net/go/gomuks/ui"
|
"maunium.net/go/gomuks/ui"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +35,6 @@ type Gomuks struct {
|
|||||||
app *tview.Application
|
app *tview.Application
|
||||||
ui *ui.GomuksUI
|
ui *ui.GomuksUI
|
||||||
matrix *matrix.Container
|
matrix *matrix.Container
|
||||||
debug *debug.Pane
|
|
||||||
debugMode bool
|
debugMode bool
|
||||||
config *config.Config
|
config *config.Config
|
||||||
stop chan bool
|
stop chan bool
|
||||||
@ -43,19 +42,14 @@ type Gomuks struct {
|
|||||||
|
|
||||||
// NewGomuks creates a new Gomuks instance with everything initialized,
|
// NewGomuks creates a new Gomuks instance with everything initialized,
|
||||||
// but does not start it.
|
// but does not start it.
|
||||||
func NewGomuks(enableDebug, forceExternalDebug bool) *Gomuks {
|
func NewGomuks(enableDebug bool) *Gomuks {
|
||||||
configDir := filepath.Join(os.Getenv("HOME"), ".config/gomuks")
|
configDir := filepath.Join(os.Getenv("HOME"), ".config/gomuks")
|
||||||
gmx := &Gomuks{
|
gmx := &Gomuks{
|
||||||
app: tview.NewApplication(),
|
app: tview.NewApplication(),
|
||||||
stop: make(chan bool, 1),
|
stop: make(chan bool, 1),
|
||||||
|
debugMode: enableDebug,
|
||||||
}
|
}
|
||||||
|
|
||||||
gmx.debug = debug.NewPane()
|
|
||||||
gmx.debug.SetChangedFunc(func() {
|
|
||||||
gmx.ui.Render()
|
|
||||||
})
|
|
||||||
debug.Default = gmx.debug
|
|
||||||
|
|
||||||
gmx.config = config.NewConfig(configDir)
|
gmx.config = config.NewConfig(configDir)
|
||||||
gmx.ui = ui.NewGomuksUI(gmx)
|
gmx.ui = ui.NewGomuksUI(gmx)
|
||||||
gmx.matrix = matrix.NewContainer(gmx)
|
gmx.matrix = matrix.NewContainer(gmx)
|
||||||
@ -68,15 +62,6 @@ func NewGomuks(enableDebug, forceExternalDebug bool) *Gomuks {
|
|||||||
_ = gmx.matrix.InitClient()
|
_ = gmx.matrix.InitClient()
|
||||||
|
|
||||||
main := gmx.ui.InitViews()
|
main := gmx.ui.InitViews()
|
||||||
if enableDebug {
|
|
||||||
debug.EnableExternal()
|
|
||||||
if forceExternalDebug {
|
|
||||||
debug.RedirectAllExt = true
|
|
||||||
} else {
|
|
||||||
main = gmx.debug.Wrap(main, debug.Right)
|
|
||||||
}
|
|
||||||
gmx.debugMode = true
|
|
||||||
}
|
|
||||||
gmx.app.SetRoot(main, true)
|
gmx.app.SetRoot(main, true)
|
||||||
|
|
||||||
return gmx
|
return gmx
|
||||||
@ -85,10 +70,10 @@ func NewGomuks(enableDebug, forceExternalDebug bool) *Gomuks {
|
|||||||
// Save saves the active session and message history.
|
// Save saves the active session and message history.
|
||||||
func (gmx *Gomuks) Save() {
|
func (gmx *Gomuks) Save() {
|
||||||
if gmx.config.Session != nil {
|
if gmx.config.Session != nil {
|
||||||
gmx.debug.Print("Saving session...")
|
debug.Print("Saving session...")
|
||||||
_ = gmx.config.Session.Save()
|
_ = gmx.config.Session.Save()
|
||||||
}
|
}
|
||||||
gmx.debug.Print("Saving history...")
|
debug.Print("Saving history...")
|
||||||
gmx.ui.MainView().SaveAllHistory()
|
gmx.ui.MainView().SaveAllHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,9 +97,9 @@ func (gmx *Gomuks) StartAutosave() {
|
|||||||
// Stop stops the Matrix syncer, the tview app and the autosave goroutine,
|
// Stop stops the Matrix syncer, the tview app and the autosave goroutine,
|
||||||
// then saves everything and calls os.Exit(0).
|
// then saves everything and calls os.Exit(0).
|
||||||
func (gmx *Gomuks) Stop() {
|
func (gmx *Gomuks) Stop() {
|
||||||
gmx.debug.Print("Disconnecting from Matrix...")
|
debug.Print("Disconnecting from Matrix...")
|
||||||
gmx.matrix.Stop()
|
gmx.matrix.Stop()
|
||||||
gmx.debug.Print("Cleaning up UI...")
|
debug.Print("Cleaning up UI...")
|
||||||
gmx.app.Stop()
|
gmx.app.Stop()
|
||||||
gmx.stop <- true
|
gmx.stop <- true
|
||||||
gmx.Save()
|
gmx.Save()
|
||||||
@ -170,8 +155,8 @@ func (gmx *Gomuks) UI() ifc.GomuksUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
debugVar := os.Getenv("DEBUG")
|
enableDebug := len(os.Getenv("DEBUG")) > 0
|
||||||
NewGomuks(len(debugVar) > 0, debugVar == "ext").Start()
|
NewGomuks(enableDebug).Start()
|
||||||
|
|
||||||
// We use os.Exit() everywhere, so exiting by returning from Start() shouldn't happen.
|
// We use os.Exit() everywhere, so exiting by returning from Start() shouldn't happen.
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
@ -17,11 +17,12 @@
|
|||||||
package ifc
|
package ifc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/types"
|
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ type GomuksUI interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MainView interface {
|
type MainView interface {
|
||||||
GetRoom(roomID string) *widget.RoomView
|
GetRoom(roomID string) RoomView
|
||||||
HasRoom(roomID string) bool
|
HasRoom(roomID string) bool
|
||||||
AddRoom(roomID string)
|
AddRoom(roomID string)
|
||||||
RemoveRoom(roomID string)
|
RemoveRoom(roomID string)
|
||||||
@ -50,11 +51,75 @@ type MainView interface {
|
|||||||
SaveAllHistory()
|
SaveAllHistory()
|
||||||
|
|
||||||
SetTyping(roomID string, users []string)
|
SetTyping(roomID string, users []string)
|
||||||
AddServiceMessage(roomID *widget.RoomView, message string)
|
ProcessMessageEvent(roomView RoomView, evt *gomatrix.Event) Message
|
||||||
ProcessMessageEvent(roomView *widget.RoomView, evt *gomatrix.Event) *types.Message
|
ProcessMembershipEvent(roomView RoomView, evt *gomatrix.Event) Message
|
||||||
ProcessMembershipEvent(roomView *widget.RoomView, evt *gomatrix.Event) *types.Message
|
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
|
||||||
NotifyMessage(room *rooms.Room, message *types.Message, should pushrules.PushActionArrayShould)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginView interface {
|
type LoginView interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MessageDirection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AppendMessage MessageDirection = iota
|
||||||
|
PrependMessage
|
||||||
|
IgnoreMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoomView interface {
|
||||||
|
MxRoom() *rooms.Room
|
||||||
|
SaveHistory(dir string) error
|
||||||
|
LoadHistory(dir string) (int, error)
|
||||||
|
|
||||||
|
SetStatus(status string)
|
||||||
|
SetTyping(users []string)
|
||||||
|
UpdateUserList()
|
||||||
|
|
||||||
|
NewMessage(id, sender, msgtype, text string, timestamp time.Time) Message
|
||||||
|
NewTempMessage(msgtype, text string) Message
|
||||||
|
AddMessage(message Message, direction MessageDirection)
|
||||||
|
AddServiceMessage(message string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageMeta interface {
|
||||||
|
Sender() string
|
||||||
|
SenderColor() tcell.Color
|
||||||
|
TextColor() tcell.Color
|
||||||
|
TimestampColor() tcell.Color
|
||||||
|
Timestamp() string
|
||||||
|
Date() string
|
||||||
|
CopyFrom(from MessageMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageState is an enum to specify if a Message is being sent, failed to send or was successfully sent.
|
||||||
|
type MessageState int
|
||||||
|
|
||||||
|
// Allowed MessageStates.
|
||||||
|
const (
|
||||||
|
MessageStateSending MessageState = iota
|
||||||
|
MessageStateDefault
|
||||||
|
MessageStateFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message interface {
|
||||||
|
MessageMeta
|
||||||
|
|
||||||
|
SetIsHighlight(isHighlight bool)
|
||||||
|
IsHighlight() bool
|
||||||
|
|
||||||
|
SetIsService(isService bool)
|
||||||
|
IsService() bool
|
||||||
|
|
||||||
|
SetID(id string)
|
||||||
|
ID() string
|
||||||
|
|
||||||
|
SetType(msgtype string)
|
||||||
|
Type() string
|
||||||
|
|
||||||
|
SetText(text string)
|
||||||
|
Text() string
|
||||||
|
|
||||||
|
SetState(state MessageState)
|
||||||
|
State() MessageState
|
||||||
|
}
|
||||||
|
@ -22,13 +22,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Container is a wrapper for a gomatrix Client and some other stuff.
|
// Container is a wrapper for a gomatrix Client and some other stuff.
|
||||||
@ -224,10 +225,10 @@ func (c *Container) HandleMessage(evt *gomatrix.Event) {
|
|||||||
message := mainView.ProcessMessageEvent(roomView, evt)
|
message := mainView.ProcessMessageEvent(roomView, evt)
|
||||||
if message != nil {
|
if message != nil {
|
||||||
if c.syncer.FirstSyncDone {
|
if c.syncer.FirstSyncDone {
|
||||||
pushRules := c.PushRules().GetActions(roomView.Room, evt).Should()
|
pushRules := c.PushRules().GetActions(roomView.MxRoom(), evt).Should()
|
||||||
mainView.NotifyMessage(roomView.Room, message, pushRules)
|
mainView.NotifyMessage(roomView.MxRoom(), message, pushRules)
|
||||||
}
|
}
|
||||||
roomView.AddMessage(message, widget.AppendMessage)
|
roomView.AddMessage(message, ifc.AppendMessage)
|
||||||
c.ui.Render()
|
c.ui.Render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -255,8 +256,7 @@ func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
|
|||||||
if evt.Unsigned.PrevContent != nil {
|
if evt.Unsigned.PrevContent != nil {
|
||||||
prevMembership, _ = evt.Unsigned.PrevContent["membership"].(string)
|
prevMembership, _ = evt.Unsigned.PrevContent["membership"].(string)
|
||||||
}
|
}
|
||||||
const Hour = 1 * 60 * 60 * 1000
|
if membership == prevMembership {
|
||||||
if membership == prevMembership || evt.Unsigned.Age > Hour {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch membership {
|
switch membership {
|
||||||
@ -282,15 +282,15 @@ func (c *Container) HandleMembership(evt *gomatrix.Event) {
|
|||||||
message := mainView.ProcessMembershipEvent(roomView, evt)
|
message := mainView.ProcessMembershipEvent(roomView, evt)
|
||||||
if message != nil {
|
if message != nil {
|
||||||
// TODO this shouldn't be necessary
|
// TODO this shouldn't be necessary
|
||||||
roomView.Room.UpdateState(evt)
|
roomView.MxRoom().UpdateState(evt)
|
||||||
// TODO This should probably also be in a different place
|
// TODO This should probably also be in a different place
|
||||||
roomView.UpdateUserList()
|
roomView.UpdateUserList()
|
||||||
|
|
||||||
if c.syncer.FirstSyncDone {
|
if c.syncer.FirstSyncDone {
|
||||||
pushRules := c.PushRules().GetActions(roomView.Room, evt).Should()
|
pushRules := c.PushRules().GetActions(roomView.MxRoom(), evt).Should()
|
||||||
mainView.NotifyMessage(roomView.Room, message, pushRules)
|
mainView.NotifyMessage(roomView.MxRoom(), message, pushRules)
|
||||||
}
|
}
|
||||||
roomView.AddMessage(message, widget.AppendMessage)
|
roomView.AddMessage(message, ifc.AppendMessage)
|
||||||
c.ui.Render()
|
c.ui.Render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
// Package debug contains utilities to display debug messages while running an interactive tview program.
|
|
||||||
package debug
|
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package widget
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
@ -24,8 +24,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/ui/types"
|
"maunium.net/go/gomuks/interface"
|
||||||
|
"maunium.net/go/gomuks/ui/messages"
|
||||||
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,11 +46,11 @@ type MessageView struct {
|
|||||||
prevHeight int
|
prevHeight int
|
||||||
prevMsgCount int
|
prevMsgCount int
|
||||||
|
|
||||||
messageIDs map[string]*types.Message
|
messageIDs map[string]messages.UIMessage
|
||||||
messages []*types.Message
|
messages []messages.UIMessage
|
||||||
|
|
||||||
textBuffer []string
|
textBuffer []string
|
||||||
metaBuffer []types.MessageMeta
|
metaBuffer []ifc.MessageMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessageView() *MessageView {
|
func NewMessageView() *MessageView {
|
||||||
@ -60,10 +62,10 @@ func NewMessageView() *MessageView {
|
|||||||
TimestampWidth: 8,
|
TimestampWidth: 8,
|
||||||
ScrollOffset: 0,
|
ScrollOffset: 0,
|
||||||
|
|
||||||
messages: make([]*types.Message, 0),
|
messages: make([]messages.UIMessage, 0),
|
||||||
messageIDs: make(map[string]*types.Message),
|
messageIDs: make(map[string]messages.UIMessage),
|
||||||
textBuffer: make([]string, 0),
|
textBuffer: make([]string, 0),
|
||||||
metaBuffer: make([]types.MessageMeta, 0),
|
metaBuffer: make([]ifc.MessageMeta, 0),
|
||||||
|
|
||||||
widestSender: 5,
|
widestSender: 5,
|
||||||
prevWidth: -1,
|
prevWidth: -1,
|
||||||
@ -72,11 +74,11 @@ func NewMessageView() *MessageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) NewMessage(id, sender, msgtype, text string, timestamp time.Time) *types.Message {
|
func (view *MessageView) NewMessage(id, sender, msgtype, text string, timestamp time.Time) messages.UIMessage {
|
||||||
return types.NewMessage(id, sender, msgtype, text,
|
return messages.NewMessage(id, sender, msgtype, text,
|
||||||
timestamp.Format(view.TimestampFormat),
|
timestamp.Format(view.TimestampFormat),
|
||||||
timestamp.Format(view.DateFormat),
|
timestamp.Format(view.DateFormat),
|
||||||
GetHashColor(sender))
|
widget.GetHashColor(sender))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) SaveHistory(path string) error {
|
func (view *MessageView) SaveHistory(path string) error {
|
||||||
@ -112,7 +114,7 @@ func (view *MessageView) LoadHistory(path string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, message := range view.messages {
|
for _, message := range view.messages {
|
||||||
view.updateWidestSender(message.Sender)
|
view.updateWidestSender(message.Sender())
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(view.messages), nil
|
return len(view.messages), nil
|
||||||
@ -127,57 +129,61 @@ func (view *MessageView) updateWidestSender(sender string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageDirection int
|
func (view *MessageView) UpdateMessageID(ifcMessage ifc.Message, newID string) {
|
||||||
|
message, ok := ifcMessage.(messages.UIMessage)
|
||||||
const (
|
if !ok {
|
||||||
AppendMessage MessageDirection = iota
|
debug.Print("[Warning] Passed non-UIMessage ifc.Message object to UpdateMessageID().")
|
||||||
PrependMessage
|
debug.PrintStack()
|
||||||
IgnoreMessage
|
return
|
||||||
)
|
}
|
||||||
|
delete(view.messageIDs, message.ID())
|
||||||
func (view *MessageView) UpdateMessageID(message *types.Message, newID string) {
|
message.SetID(newID)
|
||||||
delete(view.messageIDs, message.ID)
|
view.messageIDs[message.ID()] = message
|
||||||
message.ID = newID
|
|
||||||
view.messageIDs[message.ID] = message
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) AddMessage(message *types.Message, direction MessageDirection) {
|
func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.MessageDirection) {
|
||||||
if message == nil {
|
if ifcMessage == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message, ok := ifcMessage.(messages.UIMessage)
|
||||||
|
if !ok {
|
||||||
|
debug.Print("[Warning] Passed non-UIMessage ifc.Message object to AddMessage().")
|
||||||
|
debug.PrintStack()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, messageExists := view.messageIDs[message.ID]
|
msg, messageExists := view.messageIDs[message.ID()]
|
||||||
if msg != nil && messageExists {
|
if msg != nil && messageExists {
|
||||||
message.CopyTo(msg)
|
msg.CopyFrom(message)
|
||||||
message = msg
|
message = msg
|
||||||
direction = IgnoreMessage
|
direction = ifc.IgnoreMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
view.updateWidestSender(message.Sender)
|
view.updateWidestSender(message.Sender())
|
||||||
|
|
||||||
_, _, width, _ := view.GetInnerRect()
|
_, _, width, _ := view.GetInnerRect()
|
||||||
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
|
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
|
||||||
message.CalculateBuffer(width)
|
message.CalculateBuffer(width)
|
||||||
|
|
||||||
if direction == AppendMessage {
|
if direction == ifc.AppendMessage {
|
||||||
if view.ScrollOffset > 0 {
|
if view.ScrollOffset > 0 {
|
||||||
view.ScrollOffset += message.Height()
|
view.ScrollOffset += message.Height()
|
||||||
}
|
}
|
||||||
view.messages = append(view.messages, message)
|
view.messages = append(view.messages, message)
|
||||||
view.appendBuffer(message)
|
view.appendBuffer(message)
|
||||||
} else if direction == PrependMessage {
|
} else if direction == ifc.PrependMessage {
|
||||||
view.messages = append([]*types.Message{message}, view.messages...)
|
view.messages = append([]messages.UIMessage{message}, view.messages...)
|
||||||
}
|
}
|
||||||
|
|
||||||
view.messageIDs[message.ID] = message
|
view.messageIDs[message.ID()] = message
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) appendBuffer(message *types.Message) {
|
func (view *MessageView) appendBuffer(message messages.UIMessage) {
|
||||||
if len(view.metaBuffer) > 0 {
|
if len(view.metaBuffer) > 0 {
|
||||||
prevMeta := view.metaBuffer[len(view.metaBuffer)-1]
|
prevMeta := view.metaBuffer[len(view.metaBuffer)-1]
|
||||||
if prevMeta != nil && prevMeta.GetDate() != message.Date {
|
if prevMeta != nil && prevMeta.Date() != message.Date() {
|
||||||
view.textBuffer = append(view.textBuffer, fmt.Sprintf("Date changed to %s", message.Date))
|
view.textBuffer = append(view.textBuffer, fmt.Sprintf("Date changed to %s", message.Date()))
|
||||||
view.metaBuffer = append(view.metaBuffer, &types.BasicMeta{TextColor: tcell.ColorGreen})
|
view.metaBuffer = append(view.metaBuffer, &messages.BasicMeta{BTextColor: tcell.ColorGreen})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +201,7 @@ func (view *MessageView) recalculateBuffers() {
|
|||||||
recalculateMessageBuffers := width != view.prevWidth
|
recalculateMessageBuffers := width != view.prevWidth
|
||||||
if height != view.prevHeight || recalculateMessageBuffers || len(view.messages) != view.prevMsgCount {
|
if height != view.prevHeight || recalculateMessageBuffers || len(view.messages) != view.prevMsgCount {
|
||||||
view.textBuffer = []string{}
|
view.textBuffer = []string{}
|
||||||
view.metaBuffer = []types.MessageMeta{}
|
view.metaBuffer = []ifc.MessageMeta{}
|
||||||
view.prevMsgCount = 0
|
view.prevMsgCount = 0
|
||||||
for _, message := range view.messages {
|
for _, message := range view.messages {
|
||||||
if recalculateMessageBuffers {
|
if recalculateMessageBuffers {
|
||||||
@ -280,7 +286,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
|
|||||||
view.recalculateBuffers()
|
view.recalculateBuffers()
|
||||||
|
|
||||||
if len(view.textBuffer) == 0 {
|
if len(view.textBuffer) == 0 {
|
||||||
writeLineSimple(screen, "It's quite empty in here.", x, y+height)
|
widget.WriteLineSimple(screen, "It's quite empty in here.", x, y+height)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,11 +300,11 @@ func (view *MessageView) Draw(screen tcell.Screen) {
|
|||||||
if view.LoadingMessages {
|
if view.LoadingMessages {
|
||||||
message = "Loading more messages..."
|
message = "Loading more messages..."
|
||||||
}
|
}
|
||||||
writeLineSimpleColor(screen, message, messageX, y, tcell.ColorGreen)
|
widget.WriteLineSimpleColor(screen, message, messageX, y, tcell.ColorGreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(view.textBuffer) != len(view.metaBuffer) {
|
if len(view.textBuffer) != len(view.metaBuffer) {
|
||||||
debug.ExtPrintf("Unexpected text/meta buffer length mismatch: %d != %d.", len(view.textBuffer), len(view.metaBuffer))
|
debug.Printf("Unexpected text/meta buffer length mismatch: %d != %d.", len(view.textBuffer), len(view.metaBuffer))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +319,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
|
|||||||
scrollBarPos = height - int(math.Round(float64(view.ScrollOffset)/contentHeight*viewportHeight))
|
scrollBarPos = height - int(math.Round(float64(view.ScrollOffset)/contentHeight*viewportHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
var prevMeta types.MessageMeta
|
var prevMeta ifc.MessageMeta
|
||||||
firstLine := true
|
firstLine := true
|
||||||
skippedLines := 0
|
skippedLines := 0
|
||||||
|
|
||||||
@ -338,17 +344,17 @@ func (view *MessageView) Draw(screen tcell.Screen) {
|
|||||||
|
|
||||||
text, meta := view.textBuffer[index], view.metaBuffer[index]
|
text, meta := view.textBuffer[index], view.metaBuffer[index]
|
||||||
if meta != prevMeta {
|
if meta != prevMeta {
|
||||||
if len(meta.GetTimestamp()) > 0 {
|
if len(meta.Timestamp()) > 0 {
|
||||||
writeLineSimpleColor(screen, meta.GetTimestamp(), x, y+line, meta.GetTimestampColor())
|
widget.WriteLineSimpleColor(screen, meta.Timestamp(), x, y+line, meta.TimestampColor())
|
||||||
}
|
}
|
||||||
if prevMeta == nil || meta.GetSender() != prevMeta.GetSender() {
|
if prevMeta == nil || meta.Sender() != prevMeta.Sender() {
|
||||||
writeLineColor(
|
widget.WriteLineColor(
|
||||||
screen, tview.AlignRight, meta.GetSender(),
|
screen, tview.AlignRight, meta.Sender(),
|
||||||
usernameX, y+line, view.widestSender,
|
usernameX, y+line, view.widestSender,
|
||||||
meta.GetSenderColor())
|
meta.SenderColor())
|
||||||
}
|
}
|
||||||
prevMeta = meta
|
prevMeta = meta
|
||||||
}
|
}
|
||||||
writeLineSimpleColor(screen, text, messageX, y+line, meta.GetTextColor())
|
widget.WriteLineSimpleColor(screen, text, messageX, y+line, meta.TextColor())
|
||||||
}
|
}
|
||||||
}
|
}
|
2
ui/messages/doc.go
Normal file
2
ui/messages/doc.go
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Package types contains common type definitions used by the UI.
|
||||||
|
package messages
|
@ -14,32 +14,16 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package debug
|
package messages
|
||||||
|
|
||||||
import (
|
import "maunium.net/go/gomuks/interface"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var writer io.Writer
|
// Message is a wrapper for the content and metadata of a Matrix message intended to be displayed.
|
||||||
|
type UIMessage interface {
|
||||||
|
ifc.Message
|
||||||
|
|
||||||
func EnableExternal() {
|
CalculateBuffer(width int)
|
||||||
var err error
|
RecalculateBuffer()
|
||||||
writer, err = os.OpenFile("/tmp/gomuks-debug.log", os.O_WRONLY|os.O_APPEND, 0644)
|
Buffer() []string
|
||||||
if err != nil {
|
Height() int
|
||||||
writer = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtPrintf(text string, args ...interface{}) {
|
|
||||||
if writer != nil {
|
|
||||||
fmt.Fprintf(writer, text+"\n", args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtPrint(text ...interface{}) {
|
|
||||||
if writer != nil {
|
|
||||||
fmt.Fprintln(writer, text...)
|
|
||||||
}
|
|
||||||
}
|
}
|
70
ui/messages/meta.go
Normal file
70
ui/messages/meta.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// gomuks - A terminal Matrix client written in Go.
|
||||||
|
// Copyright (C) 2018 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package messages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
"maunium.net/go/gomuks/interface"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BasicMeta is a simple variable store implementation of MessageMeta.
|
||||||
|
type BasicMeta struct {
|
||||||
|
BSender, BTimestamp, BDate string
|
||||||
|
BSenderColor, BTextColor, BTimestampColor tcell.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sender gets the string that should be displayed as the sender of this message.
|
||||||
|
func (meta *BasicMeta) Sender() string {
|
||||||
|
return meta.BSender
|
||||||
|
}
|
||||||
|
|
||||||
|
// SenderColor returns the color the name of the sender should be shown in.
|
||||||
|
func (meta *BasicMeta) SenderColor() tcell.Color {
|
||||||
|
return meta.BSenderColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timestamp returns the formatted time when the message was sent.
|
||||||
|
func (meta *BasicMeta) Timestamp() string {
|
||||||
|
return meta.BTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date returns the formatted date when the message was sent.
|
||||||
|
func (meta *BasicMeta) Date() string {
|
||||||
|
return meta.BDate
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextColor returns the color the actual content of the message should be shown in.
|
||||||
|
func (meta *BasicMeta) TextColor() tcell.Color {
|
||||||
|
return meta.BTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimestampColor returns the color the timestamp should be shown in.
|
||||||
|
//
|
||||||
|
// This usually does not apply to the date, as it is rendered separately from the message.
|
||||||
|
func (meta *BasicMeta) TimestampColor() tcell.Color {
|
||||||
|
return meta.BTimestampColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFrom replaces the content of this meta object with the content of the given object.
|
||||||
|
func (meta *BasicMeta) CopyFrom(from ifc.MessageMeta) {
|
||||||
|
meta.BSender = from.Sender()
|
||||||
|
meta.BTimestamp = from.Timestamp()
|
||||||
|
meta.BDate = from.Date()
|
||||||
|
meta.BSenderColor = from.SenderColor()
|
||||||
|
meta.BTextColor = from.TextColor()
|
||||||
|
meta.BTimestampColor = from.TimestampColor()
|
||||||
|
}
|
295
ui/messages/textmessage.go
Normal file
295
ui/messages/textmessage.go
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
// gomuks - A terminal Matrix client written in Go.
|
||||||
|
// Copyright (C) 2018 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package messages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
|
"maunium.net/go/gomuks/interface"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(&UITextMessage{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type UITextMessage struct {
|
||||||
|
MsgID string
|
||||||
|
MsgType string
|
||||||
|
MsgSender string
|
||||||
|
MsgSenderColor tcell.Color
|
||||||
|
MsgTimestamp string
|
||||||
|
MsgDate string
|
||||||
|
MsgText string
|
||||||
|
MsgState ifc.MessageState
|
||||||
|
MsgIsHighlight bool
|
||||||
|
MsgIsService bool
|
||||||
|
buffer []string
|
||||||
|
prevBufferWidth int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMessage creates a new Message object with the provided values and the default state.
|
||||||
|
func NewMessage(id, sender, msgtype, text, timestamp, date string, senderColor tcell.Color) UIMessage {
|
||||||
|
return &UITextMessage{
|
||||||
|
MsgSender: sender,
|
||||||
|
MsgTimestamp: timestamp,
|
||||||
|
MsgDate: date,
|
||||||
|
MsgSenderColor: senderColor,
|
||||||
|
MsgType: msgtype,
|
||||||
|
MsgText: text,
|
||||||
|
MsgID: id,
|
||||||
|
prevBufferWidth: 0,
|
||||||
|
MsgState: ifc.MessageStateDefault,
|
||||||
|
MsgIsHighlight: false,
|
||||||
|
MsgIsService: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFrom replaces the content of this message object with the content of the given object.
|
||||||
|
func (msg *UITextMessage) CopyFrom(from ifc.MessageMeta) {
|
||||||
|
msg.MsgSender = from.Sender()
|
||||||
|
msg.MsgTimestamp = from.Timestamp()
|
||||||
|
msg.MsgDate = from.Date()
|
||||||
|
msg.MsgSenderColor = from.SenderColor()
|
||||||
|
|
||||||
|
fromMsg, ok := from.(UIMessage)
|
||||||
|
if ok {
|
||||||
|
msg.MsgID = fromMsg.ID()
|
||||||
|
msg.MsgType = fromMsg.Type()
|
||||||
|
msg.MsgText = fromMsg.Text()
|
||||||
|
msg.MsgState = fromMsg.State()
|
||||||
|
msg.MsgIsService = fromMsg.IsService()
|
||||||
|
msg.MsgIsHighlight = fromMsg.IsHighlight()
|
||||||
|
|
||||||
|
msg.RecalculateBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sender gets the string that should be displayed as the sender of this message.
|
||||||
|
//
|
||||||
|
// If the message is being sent, the sender is "Sending...".
|
||||||
|
// If sending has failed, the sender is "Error".
|
||||||
|
// If the message is an emote, the sender is blank.
|
||||||
|
// In any other case, the sender is the display name of the user who sent the message.
|
||||||
|
func (msg *UITextMessage) Sender() string {
|
||||||
|
switch msg.MsgState {
|
||||||
|
case ifc.MessageStateSending:
|
||||||
|
return "Sending..."
|
||||||
|
case ifc.MessageStateFailed:
|
||||||
|
return "Error"
|
||||||
|
}
|
||||||
|
switch msg.MsgType {
|
||||||
|
case "m.emote":
|
||||||
|
// Emotes don't show a separate sender, it's included in the buffer.
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return msg.MsgSender
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) getStateSpecificColor() tcell.Color {
|
||||||
|
switch msg.MsgState {
|
||||||
|
case ifc.MessageStateSending:
|
||||||
|
return tcell.ColorGray
|
||||||
|
case ifc.MessageStateFailed:
|
||||||
|
return tcell.ColorRed
|
||||||
|
case ifc.MessageStateDefault:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return tcell.ColorDefault
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SenderColor returns the color the name of the sender should be shown in.
|
||||||
|
//
|
||||||
|
// If the message is being sent, the color is gray.
|
||||||
|
// If sending has failed, the color is red.
|
||||||
|
//
|
||||||
|
// In any other case, the color is whatever is specified in the Message struct.
|
||||||
|
// Usually that means it is the hash-based color of the sender (see ui/widget/color.go)
|
||||||
|
func (msg *UITextMessage) SenderColor() tcell.Color {
|
||||||
|
stateColor := msg.getStateSpecificColor()
|
||||||
|
switch {
|
||||||
|
case stateColor != tcell.ColorDefault:
|
||||||
|
return stateColor
|
||||||
|
case msg.MsgIsService:
|
||||||
|
return tcell.ColorGray
|
||||||
|
default:
|
||||||
|
return msg.MsgSenderColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextColor returns the color the actual content of the message should be shown in.
|
||||||
|
func (msg *UITextMessage) TextColor() tcell.Color {
|
||||||
|
stateColor := msg.getStateSpecificColor()
|
||||||
|
switch {
|
||||||
|
case stateColor != tcell.ColorDefault:
|
||||||
|
return stateColor
|
||||||
|
case msg.MsgIsService:
|
||||||
|
return tcell.ColorGray
|
||||||
|
case msg.MsgIsHighlight:
|
||||||
|
return tcell.ColorYellow
|
||||||
|
case msg.MsgType == "m.room.member":
|
||||||
|
return tcell.ColorGreen
|
||||||
|
default:
|
||||||
|
return tcell.ColorDefault
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimestampColor returns the color the timestamp should be shown in.
|
||||||
|
//
|
||||||
|
// As with SenderColor(), messages being sent and messages that failed to be sent are
|
||||||
|
// gray and red respectively.
|
||||||
|
//
|
||||||
|
// However, other messages are the default color instead of a color stored in the struct.
|
||||||
|
func (msg *UITextMessage) TimestampColor() tcell.Color {
|
||||||
|
return msg.getStateSpecificColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecalculateBuffer calculates the buffer again with the previously provided width.
|
||||||
|
func (msg *UITextMessage) RecalculateBuffer() {
|
||||||
|
msg.CalculateBuffer(msg.prevBufferWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer returns the computed text buffer.
|
||||||
|
//
|
||||||
|
// The buffer contains the text of the message split into lines with a maximum
|
||||||
|
// width of whatever was provided to CalculateBuffer().
|
||||||
|
//
|
||||||
|
// N.B. This will NOT automatically calculate the buffer if it hasn't been
|
||||||
|
// calculated already, as that requires the target width.
|
||||||
|
func (msg *UITextMessage) Buffer() []string {
|
||||||
|
return msg.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height returns the number of rows in the computed buffer (see Buffer()).
|
||||||
|
func (msg *UITextMessage) Height() int {
|
||||||
|
return len(msg.buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timestamp returns the formatted time when the message was sent.
|
||||||
|
func (msg *UITextMessage) Timestamp() string {
|
||||||
|
return msg.MsgTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date returns the formatted date when the message was sent.
|
||||||
|
func (msg *UITextMessage) Date() string {
|
||||||
|
return msg.MsgDate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) ID() string {
|
||||||
|
return msg.MsgID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) SetID(id string) {
|
||||||
|
msg.MsgID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) Type() string {
|
||||||
|
return msg.MsgType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) SetType(msgtype string) {
|
||||||
|
msg.MsgType = msgtype
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) Text() string {
|
||||||
|
return msg.MsgText
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) SetText(text string) {
|
||||||
|
msg.MsgText = text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) State() ifc.MessageState {
|
||||||
|
return msg.MsgState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) SetState(state ifc.MessageState) {
|
||||||
|
msg.MsgState = state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) IsHighlight() bool {
|
||||||
|
return msg.MsgIsHighlight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) SetIsHighlight(isHighlight bool) {
|
||||||
|
msg.MsgIsHighlight = isHighlight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) IsService() bool {
|
||||||
|
return msg.MsgIsService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *UITextMessage) SetIsService(isService bool) {
|
||||||
|
msg.MsgIsService = isService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular expressions used to split lines when calculating the buffer.
|
||||||
|
//
|
||||||
|
// From tview/textview.go
|
||||||
|
var (
|
||||||
|
boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
|
||||||
|
spacePattern = regexp.MustCompile(`\s+`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// CalculateBuffer generates the internal buffer for this message that consists
|
||||||
|
// of the text of this message split into lines at most as wide as the width
|
||||||
|
// parameter.
|
||||||
|
func (msg *UITextMessage) CalculateBuffer(width int) {
|
||||||
|
if width < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.buffer = []string{}
|
||||||
|
text := msg.MsgText
|
||||||
|
if msg.MsgType == "m.emote" {
|
||||||
|
text = fmt.Sprintf("* %s %s", msg.MsgSender, msg.MsgText)
|
||||||
|
}
|
||||||
|
|
||||||
|
forcedLinebreaks := strings.Split(text, "\n")
|
||||||
|
newlines := 0
|
||||||
|
for _, str := range forcedLinebreaks {
|
||||||
|
if len(str) == 0 && newlines < 1 {
|
||||||
|
msg.buffer = append(msg.buffer, "")
|
||||||
|
newlines++
|
||||||
|
} else {
|
||||||
|
newlines = 0
|
||||||
|
}
|
||||||
|
// From tview/textview.go#reindexBuffer()
|
||||||
|
for len(str) > 0 {
|
||||||
|
extract := runewidth.Truncate(str, width, "")
|
||||||
|
if len(extract) < len(str) {
|
||||||
|
if spaces := spacePattern.FindStringIndex(str[len(extract):]); spaces != nil && spaces[0] == 0 {
|
||||||
|
extract = str[:len(extract)+spaces[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := boundaryPattern.FindAllStringIndex(extract, -1)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extract = extract[:matches[len(matches)-1][1]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg.buffer = append(msg.buffer, extract)
|
||||||
|
str = str[len(extract):]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg.prevBufferWidth = width
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package widget
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -126,11 +127,11 @@ func (list *RoomList) Draw(screen tcell.Screen) {
|
|||||||
unreadMessageCount += "!"
|
unreadMessageCount += "!"
|
||||||
}
|
}
|
||||||
unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
|
unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
|
||||||
writeLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-6, y, 6, style)
|
widget.WriteLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-6, y, 6, style)
|
||||||
lineWidth -= len(unreadMessageCount) + 1
|
lineWidth -= len(unreadMessageCount) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLine(screen, tview.AlignLeft, text, x, y, lineWidth, style)
|
widget.WriteLine(screen, tview.AlignLeft, text, x, y, lineWidth, style)
|
||||||
|
|
||||||
y++
|
y++
|
||||||
if y >= bottomLimit {
|
if y >= bottomLimit {
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package widget
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -24,8 +24,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/types"
|
"maunium.net/go/gomuks/ui/messages"
|
||||||
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,8 +38,8 @@ type RoomView struct {
|
|||||||
content *MessageView
|
content *MessageView
|
||||||
status *tview.TextView
|
status *tview.TextView
|
||||||
userList *tview.TextView
|
userList *tview.TextView
|
||||||
ulBorder *Border
|
ulBorder *widget.Border
|
||||||
input *AdvancedInputField
|
input *widget.AdvancedInputField
|
||||||
Room *rooms.Room
|
Room *rooms.Room
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,8 +50,8 @@ func NewRoomView(room *rooms.Room) *RoomView {
|
|||||||
content: NewMessageView(),
|
content: NewMessageView(),
|
||||||
status: tview.NewTextView(),
|
status: tview.NewTextView(),
|
||||||
userList: tview.NewTextView(),
|
userList: tview.NewTextView(),
|
||||||
ulBorder: NewBorder(),
|
ulBorder: widget.NewBorder(),
|
||||||
input: NewAdvancedInputField(),
|
input: widget.NewAdvancedInputField(),
|
||||||
Room: room,
|
Room: room,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +131,7 @@ func (view *RoomView) GetInputText() string {
|
|||||||
return view.input.GetText()
|
return view.input.GetText()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) GetInputField() *AdvancedInputField {
|
func (view *RoomView) GetInputField() *widget.AdvancedInputField {
|
||||||
return view.input
|
return view.input
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,15 +232,19 @@ func (view *RoomView) MessageView() *MessageView {
|
|||||||
return view.content
|
return view.content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) MxRoom() *rooms.Room {
|
||||||
|
return view.Room
|
||||||
|
}
|
||||||
|
|
||||||
func (view *RoomView) UpdateUserList() {
|
func (view *RoomView) UpdateUserList() {
|
||||||
var joined strings.Builder
|
var joined strings.Builder
|
||||||
var invited strings.Builder
|
var invited strings.Builder
|
||||||
for _, user := range view.Room.GetMembers() {
|
for _, user := range view.Room.GetMembers() {
|
||||||
if user.Membership == "join" {
|
if user.Membership == "join" {
|
||||||
joined.WriteString(AddHashColor(user.DisplayName))
|
joined.WriteString(widget.AddHashColor(user.DisplayName))
|
||||||
joined.WriteRune('\n')
|
joined.WriteRune('\n')
|
||||||
} else if user.Membership == "invite" {
|
} else if user.Membership == "invite" {
|
||||||
invited.WriteString(AddHashColor(user.DisplayName))
|
invited.WriteString(widget.AddHashColor(user.DisplayName))
|
||||||
invited.WriteRune('\n')
|
invited.WriteRune('\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,7 +255,7 @@ func (view *RoomView) UpdateUserList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) NewMessage(id, sender, msgtype, text string, timestamp time.Time) *types.Message {
|
func (view *RoomView) newUIMessage(id, sender, msgtype, text string, timestamp time.Time) messages.UIMessage {
|
||||||
member := view.Room.GetMember(sender)
|
member := view.Room.GetMember(sender)
|
||||||
if member != nil {
|
if member != nil {
|
||||||
sender = member.DisplayName
|
sender = member.DisplayName
|
||||||
@ -257,19 +263,29 @@ func (view *RoomView) NewMessage(id, sender, msgtype, text string, timestamp tim
|
|||||||
return view.content.NewMessage(id, sender, msgtype, text, timestamp)
|
return view.content.NewMessage(id, sender, msgtype, text, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) NewTempMessage(msgtype, text string) *types.Message {
|
func (view *RoomView) NewMessage(id, sender, msgtype, text string, timestamp time.Time) ifc.Message {
|
||||||
|
return view.newUIMessage(id, sender, msgtype, text, timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) NewTempMessage(msgtype, text string) ifc.Message {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
id := strconv.FormatInt(now.UnixNano(), 10)
|
id := strconv.FormatInt(now.UnixNano(), 10)
|
||||||
sender := ""
|
sender := ""
|
||||||
if ownerMember := view.Room.GetSessionOwner(); ownerMember != nil {
|
if ownerMember := view.Room.GetSessionOwner(); ownerMember != nil {
|
||||||
sender = ownerMember.DisplayName
|
sender = ownerMember.DisplayName
|
||||||
}
|
}
|
||||||
message := view.NewMessage(id, sender, msgtype, text, now)
|
message := view.newUIMessage(id, sender, msgtype, text, now)
|
||||||
message.State = types.MessageStateSending
|
message.SetState(ifc.MessageStateSending)
|
||||||
view.AddMessage(message, AppendMessage)
|
view.AddMessage(message, ifc.AppendMessage)
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) AddMessage(message *types.Message, direction MessageDirection) {
|
func (view *RoomView) AddServiceMessage(text string) {
|
||||||
|
message := view.newUIMessage("", "*", "gomuks.service", text, time.Now())
|
||||||
|
message.SetIsService(true)
|
||||||
|
view.AddMessage(message, ifc.AppendMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) AddMessage(message ifc.Message, direction ifc.MessageDirection) {
|
||||||
view.content.AddMessage(message, direction)
|
view.content.AddMessage(message, direction)
|
||||||
}
|
}
|
@ -1,2 +0,0 @@
|
|||||||
// Package types contains common type definitions used mostly by the UI, but also other parts of gomuks.
|
|
||||||
package types
|
|
@ -1,234 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MessageState is an enum to specify if a Message is being sent, failed to send or was successfully sent.
|
|
||||||
type MessageState int
|
|
||||||
|
|
||||||
// Allowed MessageStates.
|
|
||||||
const (
|
|
||||||
MessageStateSending MessageState = iota
|
|
||||||
MessageStateDefault
|
|
||||||
MessageStateFailed
|
|
||||||
)
|
|
||||||
|
|
||||||
// Message is a wrapper for the content and metadata of a Matrix message intended to be displayed.
|
|
||||||
type Message struct {
|
|
||||||
ID string
|
|
||||||
Type string
|
|
||||||
Sender string
|
|
||||||
SenderColor tcell.Color
|
|
||||||
TextColor tcell.Color
|
|
||||||
Timestamp string
|
|
||||||
Date string
|
|
||||||
Text string
|
|
||||||
State MessageState
|
|
||||||
buffer []string
|
|
||||||
prevBufferWidth int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMessage creates a new Message object with the provided values and the default state.
|
|
||||||
func NewMessage(id, sender, msgtype, text, timestamp, date string, senderColor tcell.Color) *Message {
|
|
||||||
return &Message{
|
|
||||||
Sender: sender,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
Date: date,
|
|
||||||
SenderColor: senderColor,
|
|
||||||
TextColor: tcell.ColorDefault,
|
|
||||||
Type: msgtype,
|
|
||||||
Text: text,
|
|
||||||
ID: id,
|
|
||||||
prevBufferWidth: 0,
|
|
||||||
State: MessageStateDefault,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyTo copies the content of this message to the given message.
|
|
||||||
func (message *Message) CopyTo(to *Message) {
|
|
||||||
to.ID = message.ID
|
|
||||||
to.Type = message.Type
|
|
||||||
to.Sender = message.Sender
|
|
||||||
to.SenderColor = message.SenderColor
|
|
||||||
to.TextColor = message.TextColor
|
|
||||||
to.Timestamp = message.Timestamp
|
|
||||||
to.Date = message.Date
|
|
||||||
to.Text = message.Text
|
|
||||||
to.State = message.State
|
|
||||||
to.RecalculateBuffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSender gets the string that should be displayed as the sender of this message.
|
|
||||||
//
|
|
||||||
// If the message is being sent, the sender is "Sending...".
|
|
||||||
// If sending has failed, the sender is "Error".
|
|
||||||
// If the message is an emote, the sender is blank.
|
|
||||||
// In any other case, the sender is the display name of the user who sent the message.
|
|
||||||
func (message *Message) GetSender() string {
|
|
||||||
switch message.State {
|
|
||||||
case MessageStateSending:
|
|
||||||
return "Sending..."
|
|
||||||
case MessageStateFailed:
|
|
||||||
return "Error"
|
|
||||||
}
|
|
||||||
switch message.Type {
|
|
||||||
case "m.emote":
|
|
||||||
// Emotes don't show a separate sender, it's included in the buffer.
|
|
||||||
return ""
|
|
||||||
default:
|
|
||||||
return message.Sender
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (message *Message) getStateSpecificColor() tcell.Color {
|
|
||||||
switch message.State {
|
|
||||||
case MessageStateSending:
|
|
||||||
return tcell.ColorGray
|
|
||||||
case MessageStateFailed:
|
|
||||||
return tcell.ColorRed
|
|
||||||
case MessageStateDefault:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return tcell.ColorDefault
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSenderColor returns the color the name of the sender should be shown in.
|
|
||||||
//
|
|
||||||
// If the message is being sent, the color is gray.
|
|
||||||
// If sending has failed, the color is red.
|
|
||||||
//
|
|
||||||
// In any other case, the color is whatever is specified in the Message struct.
|
|
||||||
// Usually that means it is the hash-based color of the sender (see ui/widget/color.go)
|
|
||||||
func (message *Message) GetSenderColor() (color tcell.Color) {
|
|
||||||
color = message.getStateSpecificColor()
|
|
||||||
if color == tcell.ColorDefault {
|
|
||||||
color = message.SenderColor
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTextColor returns the color the actual content of the message should be shown in.
|
|
||||||
//
|
|
||||||
// This returns the same colors as GetSenderColor(), but takes the default color from a different variable.
|
|
||||||
func (message *Message) GetTextColor() (color tcell.Color) {
|
|
||||||
color = message.getStateSpecificColor()
|
|
||||||
if color == tcell.ColorDefault {
|
|
||||||
color = message.TextColor
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimestampColor returns the color the timestamp should be shown in.
|
|
||||||
//
|
|
||||||
// As with GetSenderColor(), messages being sent and messages that failed to be sent are
|
|
||||||
// gray and red respectively.
|
|
||||||
//
|
|
||||||
// However, other messages are the default color instead of a color stored in the struct.
|
|
||||||
func (message *Message) GetTimestampColor() tcell.Color {
|
|
||||||
return message.getStateSpecificColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecalculateBuffer calculates the buffer again with the previously provided width.
|
|
||||||
func (message *Message) RecalculateBuffer() {
|
|
||||||
message.CalculateBuffer(message.prevBufferWidth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffer returns the computed text buffer.
|
|
||||||
//
|
|
||||||
// The buffer contains the text of the message split into lines with a maximum
|
|
||||||
// width of whatever was provided to CalculateBuffer().
|
|
||||||
//
|
|
||||||
// N.B. This will NOT automatically calculate the buffer if it hasn't been
|
|
||||||
// calculated already, as that requires the target width.
|
|
||||||
func (message *Message) Buffer() []string {
|
|
||||||
return message.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Height returns the number of rows in the computed buffer (see Buffer()).
|
|
||||||
func (message *Message) Height() int {
|
|
||||||
return len(message.buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimestamp returns the formatted time when the message was sent.
|
|
||||||
func (message *Message) GetTimestamp() string {
|
|
||||||
return message.Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDate returns the formatted date when the message was sent.
|
|
||||||
func (message *Message) GetDate() string {
|
|
||||||
return message.Date
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular expressions used to split lines when calculating the buffer.
|
|
||||||
//
|
|
||||||
// From tview/textview.go
|
|
||||||
var (
|
|
||||||
boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
|
|
||||||
spacePattern = regexp.MustCompile(`\s+`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// CalculateBuffer generates the internal buffer for this message that consists
|
|
||||||
// of the text of this message split into lines at most as wide as the width
|
|
||||||
// parameter.
|
|
||||||
func (message *Message) CalculateBuffer(width int) {
|
|
||||||
if width < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
message.buffer = []string{}
|
|
||||||
text := message.Text
|
|
||||||
if message.Type == "m.emote" {
|
|
||||||
text = fmt.Sprintf("* %s %s", message.Sender, message.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
forcedLinebreaks := strings.Split(text, "\n")
|
|
||||||
newlines := 0
|
|
||||||
for _, str := range forcedLinebreaks {
|
|
||||||
if len(str) == 0 && newlines < 1 {
|
|
||||||
message.buffer = append(message.buffer, "")
|
|
||||||
newlines++
|
|
||||||
} else {
|
|
||||||
newlines = 0
|
|
||||||
}
|
|
||||||
// From tview/textview.go#reindexBuffer()
|
|
||||||
for len(str) > 0 {
|
|
||||||
extract := runewidth.Truncate(str, width, "")
|
|
||||||
if len(extract) < len(str) {
|
|
||||||
if spaces := spacePattern.FindStringIndex(str[len(extract):]); spaces != nil && spaces[0] == 0 {
|
|
||||||
extract = str[:len(extract)+spaces[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := boundaryPattern.FindAllStringIndex(extract, -1)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
extract = extract[:matches[len(matches)-1][1]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
message.buffer = append(message.buffer, extract)
|
|
||||||
str = str[len(extract):]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
message.prevBufferWidth = width
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gdamore/tcell"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MessageMeta is an interface to get the metadata of a message.
|
|
||||||
//
|
|
||||||
// See BasicMeta for a simple implementation and documentation of methods.
|
|
||||||
type MessageMeta interface {
|
|
||||||
GetSender() string
|
|
||||||
GetSenderColor() tcell.Color
|
|
||||||
GetTextColor() tcell.Color
|
|
||||||
GetTimestampColor() tcell.Color
|
|
||||||
GetTimestamp() string
|
|
||||||
GetDate() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasicMeta is a simple variable store implementation of MessageMeta.
|
|
||||||
type BasicMeta struct {
|
|
||||||
Sender, Timestamp, Date string
|
|
||||||
SenderColor, TextColor, TimestampColor tcell.Color
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSender gets the string that should be displayed as the sender of this message.
|
|
||||||
func (meta *BasicMeta) GetSender() string {
|
|
||||||
return meta.Sender
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSenderColor returns the color the name of the sender should be shown in.
|
|
||||||
func (meta *BasicMeta) GetSenderColor() tcell.Color {
|
|
||||||
return meta.SenderColor
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimestamp returns the formatted time when the message was sent.
|
|
||||||
func (meta *BasicMeta) GetTimestamp() string {
|
|
||||||
return meta.Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDate returns the formatted date when the message was sent.
|
|
||||||
func (meta *BasicMeta) GetDate() string {
|
|
||||||
return meta.Date
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTextColor returns the color the actual content of the message should be shown in.
|
|
||||||
func (meta *BasicMeta) GetTextColor() tcell.Color {
|
|
||||||
return meta.TextColor
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimestampColor returns the color the timestamp should be shown in.
|
|
||||||
//
|
|
||||||
// This usually does not apply to the date, as it is rendered separately from the message.
|
|
||||||
func (meta *BasicMeta) GetTimestampColor() tcell.Color {
|
|
||||||
return meta.TimestampColor
|
|
||||||
}
|
|
@ -19,7 +19,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
@ -27,12 +27,11 @@ import (
|
|||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/notification"
|
"maunium.net/go/gomuks/notification"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
|
||||||
"maunium.net/go/gomuks/ui/types"
|
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
@ -40,9 +39,9 @@ import (
|
|||||||
type MainView struct {
|
type MainView struct {
|
||||||
*tview.Flex
|
*tview.Flex
|
||||||
|
|
||||||
roomList *widget.RoomList
|
roomList *RoomList
|
||||||
roomView *tview.Pages
|
roomView *tview.Pages
|
||||||
rooms map[string]*widget.RoomView
|
rooms map[string]*RoomView
|
||||||
currentRoomIndex int
|
currentRoomIndex int
|
||||||
roomIDs []string
|
roomIDs []string
|
||||||
|
|
||||||
@ -57,9 +56,9 @@ type MainView struct {
|
|||||||
func (ui *GomuksUI) NewMainView() tview.Primitive {
|
func (ui *GomuksUI) NewMainView() tview.Primitive {
|
||||||
mainView := &MainView{
|
mainView := &MainView{
|
||||||
Flex: tview.NewFlex(),
|
Flex: tview.NewFlex(),
|
||||||
roomList: widget.NewRoomList(),
|
roomList: NewRoomList(),
|
||||||
roomView: tview.NewPages(),
|
roomView: tview.NewPages(),
|
||||||
rooms: make(map[string]*widget.RoomView),
|
rooms: make(map[string]*RoomView),
|
||||||
|
|
||||||
matrix: ui.gmx.Matrix(),
|
matrix: ui.gmx.Matrix(),
|
||||||
gmx: ui.gmx,
|
gmx: ui.gmx,
|
||||||
@ -81,7 +80,7 @@ func (view *MainView) BumpFocus() {
|
|||||||
view.lastFocusTime = time.Now()
|
view.lastFocusTime = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) InputChanged(roomView *widget.RoomView, text string) {
|
func (view *MainView) InputChanged(roomView *RoomView, text string) {
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
go view.matrix.SendTyping(roomView.Room.ID, false)
|
go view.matrix.SendTyping(roomView.Room.ID, false)
|
||||||
} else if text[0] != '/' {
|
} else if text[0] != '/' {
|
||||||
@ -101,7 +100,7 @@ func findWordToTabComplete(text string) string {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) InputTabComplete(roomView *widget.RoomView, text string, cursorOffset int) string {
|
func (view *MainView) InputTabComplete(roomView *RoomView, text string, cursorOffset int) string {
|
||||||
str := runewidth.Truncate(text, cursorOffset, "")
|
str := runewidth.Truncate(text, cursorOffset, "")
|
||||||
word := findWordToTabComplete(str)
|
word := findWordToTabComplete(str)
|
||||||
userCompletions := roomView.AutocompleteUser(word)
|
userCompletions := roomView.AutocompleteUser(word)
|
||||||
@ -118,7 +117,7 @@ func (view *MainView) InputTabComplete(roomView *widget.RoomView, text string, c
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) InputSubmit(roomView *widget.RoomView, text string) {
|
func (view *MainView) InputSubmit(roomView *RoomView, text string) {
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return
|
return
|
||||||
} else if text[0] == '/' {
|
} else if text[0] == '/' {
|
||||||
@ -132,23 +131,23 @@ func (view *MainView) InputSubmit(roomView *widget.RoomView, text string) {
|
|||||||
roomView.SetInputText("")
|
roomView.SetInputText("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) SendMessage(roomView *widget.RoomView, text string) {
|
func (view *MainView) SendMessage(roomView *RoomView, text string) {
|
||||||
tempMessage := roomView.NewTempMessage("m.text", text)
|
tempMessage := roomView.NewTempMessage("m.text", text)
|
||||||
go view.sendTempMessage(roomView, tempMessage)
|
go view.sendTempMessage(roomView, tempMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) sendTempMessage(roomView *widget.RoomView, tempMessage *types.Message) {
|
func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Message) {
|
||||||
defer view.gmx.Recover()
|
defer view.gmx.Recover()
|
||||||
eventID, err := view.matrix.SendMessage(roomView.Room.ID, tempMessage.Type, tempMessage.Text)
|
eventID, err := view.matrix.SendMessage(roomView.Room.ID, tempMessage.Type(), tempMessage.Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tempMessage.State = types.MessageStateFailed
|
tempMessage.SetState(ifc.MessageStateFailed)
|
||||||
roomView.SetStatus(fmt.Sprintf("Failed to send message: %s", err))
|
roomView.SetStatus(fmt.Sprintf("Failed to send message: %s", err))
|
||||||
} else {
|
} else {
|
||||||
roomView.MessageView().UpdateMessageID(tempMessage, eventID)
|
roomView.MessageView().UpdateMessageID(tempMessage, eventID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) HandleCommand(roomView *widget.RoomView, command string, args []string) {
|
func (view *MainView) HandleCommand(roomView *RoomView, command string, args []string) {
|
||||||
defer view.gmx.Recover()
|
defer view.gmx.Recover()
|
||||||
debug.Print("Handling command", command, args)
|
debug.Print("Handling command", command, args)
|
||||||
switch command {
|
switch command {
|
||||||
@ -169,16 +168,16 @@ func (view *MainView) HandleCommand(roomView *widget.RoomView, command string, a
|
|||||||
debug.Print("Leave room result:", view.matrix.LeaveRoom(roomView.Room.ID))
|
debug.Print("Leave room result:", view.matrix.LeaveRoom(roomView.Room.ID))
|
||||||
case "/join":
|
case "/join":
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
view.AddServiceMessage(roomView, "Usage: /join <room>")
|
roomView.AddServiceMessage("Usage: /join <room>")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
debug.Print("Join room result:", view.matrix.JoinRoom(args[0]))
|
debug.Print("Join room result:", view.matrix.JoinRoom(args[0]))
|
||||||
default:
|
default:
|
||||||
view.AddServiceMessage(roomView, "Unknown command.")
|
roomView.AddServiceMessage("Unknown command.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) KeyEventHandler(roomView *widget.RoomView, key *tcell.EventKey) *tcell.EventKey {
|
func (view *MainView) KeyEventHandler(roomView *RoomView, key *tcell.EventKey) *tcell.EventKey {
|
||||||
view.BumpFocus()
|
view.BumpFocus()
|
||||||
|
|
||||||
k := key.Key()
|
k := key.Key()
|
||||||
@ -220,7 +219,7 @@ func (view *MainView) KeyEventHandler(roomView *widget.RoomView, key *tcell.Even
|
|||||||
|
|
||||||
const WheelScrollOffsetDiff = 3
|
const WheelScrollOffsetDiff = 3
|
||||||
|
|
||||||
func (view *MainView) MouseEventHandler(roomView *widget.RoomView, event *tcell.EventMouse) *tcell.EventMouse {
|
func (view *MainView) MouseEventHandler(roomView *RoomView, event *tcell.EventMouse) *tcell.EventMouse {
|
||||||
if event.Buttons() == tcell.ButtonNone {
|
if event.Buttons() == tcell.ButtonNone {
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
@ -300,7 +299,7 @@ func (view *MainView) addRoom(index int, room string) {
|
|||||||
|
|
||||||
view.roomList.Add(roomStore)
|
view.roomList.Add(roomStore)
|
||||||
if !view.roomView.HasPage(room) {
|
if !view.roomView.HasPage(room) {
|
||||||
roomView := widget.NewRoomView(roomStore).
|
roomView := NewRoomView(roomStore).
|
||||||
SetInputSubmitFunc(view.InputSubmit).
|
SetInputSubmitFunc(view.InputSubmit).
|
||||||
SetInputChangedFunc(view.InputChanged).
|
SetInputChangedFunc(view.InputChanged).
|
||||||
SetTabCompleteFunc(view.InputTabComplete).
|
SetTabCompleteFunc(view.InputTabComplete).
|
||||||
@ -319,7 +318,7 @@ func (view *MainView) addRoom(index int, room string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) GetRoom(id string) *widget.RoomView {
|
func (view *MainView) GetRoom(id string) ifc.RoomView {
|
||||||
return view.rooms[id]
|
return view.rooms[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,7 +351,7 @@ func (view *MainView) RemoveRoom(room string) {
|
|||||||
} else {
|
} else {
|
||||||
removeIndex = sort.StringSlice(view.roomIDs).Search(room)
|
removeIndex = sort.StringSlice(view.roomIDs).Search(room)
|
||||||
}
|
}
|
||||||
view.roomList.Remove(roomView.Room)
|
view.roomList.Remove(roomView.MxRoom())
|
||||||
view.roomIDs = append(view.roomIDs[:removeIndex], view.roomIDs[removeIndex+1:]...)
|
view.roomIDs = append(view.roomIDs[:removeIndex], view.roomIDs[removeIndex+1:]...)
|
||||||
view.roomView.RemovePage(room)
|
view.roomView.RemovePage(room)
|
||||||
delete(view.rooms, room)
|
delete(view.rooms, room)
|
||||||
@ -363,7 +362,7 @@ func (view *MainView) SetRooms(rooms []string) {
|
|||||||
view.roomIDs = rooms
|
view.roomIDs = rooms
|
||||||
view.roomList.Clear()
|
view.roomList.Clear()
|
||||||
view.roomView.Clear()
|
view.roomView.Clear()
|
||||||
view.rooms = make(map[string]*widget.RoomView)
|
view.rooms = make(map[string]*RoomView)
|
||||||
for index, room := range rooms {
|
for index, room := range rooms {
|
||||||
view.addRoom(index, room)
|
view.addRoom(index, room)
|
||||||
}
|
}
|
||||||
@ -385,14 +384,14 @@ func sendNotification(room *rooms.Room, sender, text string, critical, sound boo
|
|||||||
notification.Send(sender, text, critical, sound)
|
notification.Send(sender, text, critical, sound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) NotifyMessage(room *rooms.Room, message *types.Message, should pushrules.PushActionArrayShould) {
|
func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) {
|
||||||
// Whether or not the room where the message came is the currently shown room.
|
// Whether or not the room where the message came is the currently shown room.
|
||||||
isCurrent := room.ID == view.CurrentRoomID()
|
isCurrent := room.ID == view.CurrentRoomID()
|
||||||
// Whether or not the terminal window is focused.
|
// Whether or not the terminal window is focused.
|
||||||
isFocused := view.lastFocusTime.Add(30 * time.Second).Before(time.Now())
|
isFocused := view.lastFocusTime.Add(30 * time.Second).Before(time.Now())
|
||||||
|
|
||||||
// Whether or not the push rules say this message should be notified about.
|
// Whether or not the push rules say this message should be notified about.
|
||||||
shouldNotify := (should.Notify || !should.NotifySpecified) && message.Sender != view.config.Session.UserID
|
shouldNotify := (should.Notify || !should.NotifySpecified) && message.Sender() != view.config.Session.UserID
|
||||||
|
|
||||||
if !isCurrent {
|
if !isCurrent {
|
||||||
// The message is not in the current room, show new message status in room list.
|
// The message is not in the current room, show new message status in room list.
|
||||||
@ -406,21 +405,10 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message *types.Message, sh
|
|||||||
if shouldNotify && !isFocused {
|
if shouldNotify && !isFocused {
|
||||||
// Push rules say notify and the terminal is not focused, send desktop notification.
|
// Push rules say notify and the terminal is not focused, send desktop notification.
|
||||||
shouldPlaySound := should.PlaySound && should.SoundName == "default"
|
shouldPlaySound := should.PlaySound && should.SoundName == "default"
|
||||||
sendNotification(room, message.Sender, message.Text, should.Highlight, shouldPlaySound)
|
sendNotification(room, message.Sender(), message.Text(), should.Highlight, shouldPlaySound)
|
||||||
}
|
}
|
||||||
|
|
||||||
if should.Highlight {
|
message.SetIsHighlight(should.Highlight)
|
||||||
// Message is highlight, set color.
|
|
||||||
message.TextColor = tcell.ColorYellow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *MainView) AddServiceMessage(roomView *widget.RoomView, text string) {
|
|
||||||
message := roomView.NewMessage("", "*", "gomuks.service", text, time.Now())
|
|
||||||
message.TextColor = tcell.ColorGray
|
|
||||||
message.SenderColor = tcell.ColorGray
|
|
||||||
roomView.AddMessage(message, widget.AppendMessage)
|
|
||||||
view.parent.Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) LoadHistory(room string, initial bool) {
|
func (view *MainView) LoadHistory(room string, initial bool) {
|
||||||
@ -452,20 +440,20 @@ func (view *MainView) LoadHistory(room string, initial bool) {
|
|||||||
}
|
}
|
||||||
history, prevBatch, err := view.matrix.GetHistory(roomView.Room.ID, batch, 50)
|
history, prevBatch, err := view.matrix.GetHistory(roomView.Room.ID, batch, 50)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
view.AddServiceMessage(roomView, "Failed to fetch history")
|
roomView.AddServiceMessage("Failed to fetch history")
|
||||||
debug.Print("Failed to fetch history for", roomView.Room.ID, err)
|
debug.Print("Failed to fetch history for", roomView.Room.ID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
roomView.Room.PrevBatch = prevBatch
|
roomView.Room.PrevBatch = prevBatch
|
||||||
for _, evt := range history {
|
for _, evt := range history {
|
||||||
var message *types.Message
|
var message ifc.Message
|
||||||
if evt.Type == "m.room.message" {
|
if evt.Type == "m.room.message" {
|
||||||
message = view.ProcessMessageEvent(roomView, &evt)
|
message = view.ProcessMessageEvent(roomView, &evt)
|
||||||
} else if evt.Type == "m.room.member" {
|
} else if evt.Type == "m.room.member" {
|
||||||
message = view.ProcessMembershipEvent(roomView, &evt)
|
message = view.ProcessMembershipEvent(roomView, &evt)
|
||||||
}
|
}
|
||||||
if message != nil {
|
if message != nil {
|
||||||
roomView.AddMessage(message, widget.PrependMessage)
|
roomView.AddMessage(message, ifc.PrependMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = roomView.SaveHistory(view.config.HistoryDir)
|
err = roomView.SaveHistory(view.config.HistoryDir)
|
||||||
@ -476,7 +464,7 @@ func (view *MainView) LoadHistory(room string, initial bool) {
|
|||||||
view.parent.Render()
|
view.parent.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) ProcessMessageEvent(room *widget.RoomView, evt *gomatrix.Event) (message *types.Message) {
|
func (view *MainView) ProcessMessageEvent(room ifc.RoomView, evt *gomatrix.Event) ifc.Message {
|
||||||
text, _ := evt.Content["body"].(string)
|
text, _ := evt.Content["body"].(string)
|
||||||
msgtype, _ := evt.Content["msgtype"].(string)
|
msgtype, _ := evt.Content["msgtype"].(string)
|
||||||
return room.NewMessage(evt.ID, evt.Sender, msgtype, text, unixToTime(evt.Timestamp))
|
return room.NewMessage(evt.ID, evt.Sender, msgtype, text, unixToTime(evt.Timestamp))
|
||||||
@ -519,14 +507,12 @@ func (view *MainView) getMembershipEventContent(evt *gomatrix.Event) (sender, te
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) ProcessMembershipEvent(room *widget.RoomView, evt *gomatrix.Event) (message *types.Message) {
|
func (view *MainView) ProcessMembershipEvent(room ifc.RoomView, evt *gomatrix.Event) ifc.Message {
|
||||||
sender, text := view.getMembershipEventContent(evt)
|
sender, text := view.getMembershipEventContent(evt)
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
message = room.NewMessage(evt.ID, sender, "m.room.member", text, unixToTime(evt.Timestamp))
|
return room.NewMessage(evt.ID, sender, "m.room.member", text, unixToTime(evt.Timestamp))
|
||||||
message.TextColor = tcell.ColorGreen
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func unixToTime(unix int64) time.Time {
|
func unixToTime(unix int64) time.Time {
|
||||||
|
@ -22,19 +22,19 @@ import (
|
|||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeLineSimple(screen tcell.Screen, line string, x, y int) {
|
func WriteLineSimple(screen tcell.Screen, line string, x, y int) {
|
||||||
writeLine(screen, tview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault)
|
WriteLine(screen, tview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeLineSimpleColor(screen tcell.Screen, line string, x, y int, color tcell.Color) {
|
func WriteLineSimpleColor(screen tcell.Screen, line string, x, y int, color tcell.Color) {
|
||||||
writeLine(screen, tview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault.Foreground(color))
|
WriteLine(screen, tview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault.Foreground(color))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeLineColor(screen tcell.Screen, align int, line string, x, y, maxWidth int, color tcell.Color) {
|
func WriteLineColor(screen tcell.Screen, align int, line string, x, y, maxWidth int, color tcell.Color) {
|
||||||
writeLine(screen, align, line, x, y, maxWidth, tcell.StyleDefault.Foreground(color))
|
WriteLine(screen, align, line, x, y, maxWidth, tcell.StyleDefault.Foreground(color))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeLine(screen tcell.Screen, align int, line string, x, y, maxWidth int, style tcell.Style) {
|
func WriteLine(screen tcell.Screen, align int, line string, x, y, maxWidth int, style tcell.Style) {
|
||||||
offsetX := 0
|
offsetX := 0
|
||||||
if align == tview.AlignRight {
|
if align == tview.AlignRight {
|
||||||
offsetX = maxWidth - runewidth.StringWidth(line)
|
offsetX = maxWidth - runewidth.StringWidth(line)
|
||||||
|
Loading…
Reference in New Issue
Block a user