aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTulir Asokan <tulir@maunium.net>2018-03-18 21:24:03 +0200
committerTulir Asokan <tulir@maunium.net>2018-03-18 21:24:03 +0200
commit72945c9a284b6858594f1e8a43743c397e90c380 (patch)
treec4dc096f97c546dcc546d50385e2909e2e10b82d
parent0509b195625c959a7b5556e3baae4f869c4d62f6 (diff)
Organize files
-rw-r--r--config/config.go (renamed from config.go)17
-rw-r--r--config/session.go (renamed from session.go)27
-rw-r--r--go.mod14
-rw-r--r--gomuks.go60
-rw-r--r--interface/gomuks.go35
-rw-r--r--interface/matrix.go40
-rw-r--r--interface/ui.go54
-rw-r--r--matrix/matrix.go (renamed from matrix.go)105
-rw-r--r--matrix/room/member.go51
-rw-r--r--matrix/room/room.go (renamed from room.go)42
-rw-r--r--matrix/sync.go (renamed from sync.go)27
-rw-r--r--ui/debug/debug.go (renamed from debug.go)64
-rw-r--r--ui/types/message.go85
-rw-r--r--ui/ui.go (renamed from ui.go)29
-rw-r--r--ui/view-login.go (renamed from view-login.go)19
-rw-r--r--ui/view-main.go (renamed from view-main.go)64
-rw-r--r--ui/widget/advanced-inputfield.go (renamed from advanced-inputfield.go)2
-rw-r--r--ui/widget/border.go (renamed from border.go)2
-rw-r--r--ui/widget/center.go32
-rw-r--r--ui/widget/color.go60
-rw-r--r--ui/widget/form-text-view.go (renamed from uiutil.go)13
-rw-r--r--ui/widget/message-view.go (renamed from message-view.go)97
-rw-r--r--ui/widget/room-view.go (renamed from room-view.go)73
23 files changed, 620 insertions, 392 deletions
diff --git a/config.go b/config/config.go
index f8696a4..d4ede80 100644
--- a/config.go
+++ b/config/config.go
@@ -14,7 +14,7 @@
// 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 main
+package config
import (
"fmt"
@@ -23,22 +23,19 @@ import (
"path/filepath"
"gopkg.in/yaml.v2"
+ "maunium.net/go/gomuks/ui/debug"
)
type Config struct {
MXID string `yaml:"mxid"`
HS string `yaml:"homeserver"`
- dir string `yaml:"-"`
- gmx Gomuks `yaml:"-"`
- debug DebugPrinter `yaml:"-"`
- Session *Session `yaml:"-"`
+ dir string `yaml:"-"`
+ Session *Session `yaml:"-"`
}
-func NewConfig(gmx Gomuks, dir string) *Config {
+func NewConfig(dir string) *Config {
return &Config{
- gmx: gmx,
- debug: gmx.Debug(),
dir: dir,
}
}
@@ -67,14 +64,14 @@ func (config *Config) Save() {
os.MkdirAll(config.dir, 0700)
data, err := yaml.Marshal(&config)
if err != nil {
- config.debug.Print("Failed to marshal config")
+ debug.Print("Failed to marshal config")
panic(err)
}
path := filepath.Join(config.dir, "config.yaml")
err = ioutil.WriteFile(path, data, 0600)
if err != nil {
- config.debug.Print("Failed to write config to", path)
+ debug.Print("Failed to write config to", path)
panic(err)
}
}
diff --git a/session.go b/config/session.go
index eda49dc..a90fc20 100644
--- a/session.go
+++ b/config/session.go
@@ -14,7 +14,7 @@
// 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 main
+package config
import (
"encoding/json"
@@ -22,6 +22,8 @@ import (
"path/filepath"
"maunium.net/go/gomatrix"
+ rooms "maunium.net/go/gomuks/matrix/room"
+ "maunium.net/go/gomuks/ui/debug"
)
type Session struct {
@@ -30,9 +32,7 @@ type Session struct {
AccessToken string
NextBatch string
FilterID string
- Rooms map[string]*Room
-
- debug DebugPrinter `json:"-"`
+ Rooms map[string]*rooms.Room
}
func (config *Config) LoadSession(mxid string) {
@@ -44,13 +44,12 @@ func (config *Config) NewSession(mxid string) *Session {
return &Session{
MXID: mxid,
path: filepath.Join(config.dir, mxid+".session"),
- Rooms: make(map[string]*Room),
- debug: config.debug,
+ Rooms: make(map[string]*rooms.Room),
}
}
func (s *Session) Clear() {
- s.Rooms = make(map[string]*Room)
+ s.Rooms = make(map[string]*rooms.Room)
s.NextBatch = ""
s.FilterID = ""
s.Save()
@@ -59,13 +58,13 @@ func (s *Session) Clear() {
func (s *Session) Load() {
data, err := ioutil.ReadFile(s.path)
if err != nil {
- s.debug.Print("Failed to read session from", s.path)
+ debug.Print("Failed to read session from", s.path)
panic(err)
}
err = json.Unmarshal(data, s)
if err != nil {
- s.debug.Print("Failed to parse session at", s.path)
+ debug.Print("Failed to parse session at", s.path)
panic(err)
}
}
@@ -73,13 +72,13 @@ func (s *Session) Load() {
func (s *Session) Save() {
data, err := json.Marshal(s)
if err != nil {
- s.debug.Print("Failed to marshal session of", s.MXID)
+ debug.Print("Failed to marshal session of", s.MXID)
panic(err)
}
err = ioutil.WriteFile(s.path, data, 0600)
if err != nil {
- s.debug.Print("Failed to write session to", s.path)
+ debug.Print("Failed to write session to", s.path)
panic(err)
}
}
@@ -92,16 +91,16 @@ func (s *Session) LoadNextBatch(_ string) string {
return s.NextBatch
}
-func (s *Session) GetRoom(mxid string) *Room {
+func (s *Session) GetRoom(mxid string) *rooms.Room {
room, _ := s.Rooms[mxid]
if room == nil {
- room = NewRoom(mxid)
+ room = rooms.NewRoom(mxid)
s.Rooms[room.ID] = room
}
return room
}
-func (s *Session) PutRoom(room *Room) {
+func (s *Session) PutRoom(room *rooms.Room) {
s.Rooms[room.ID] = room
s.Save()
}
diff --git a/go.mod b/go.mod
deleted file mode 100644
index 4888c39..0000000
--- a/go.mod
+++ /dev/null
@@ -1,14 +0,0 @@
-module "maunium.net/go/gomuks"
-
-require (
- "github.com/gdamore/encoding" v0.0.0-20151215212835-b23993cbb635
- "github.com/gdamore/tcell" v1.0.0
- "github.com/jroimartin/gocui" v0.0.0-20170827195011-4f518eddb04b
- "github.com/lucasb-eyer/go-colorful" v0.0.0-20170903184257-231272389856
- "github.com/matrix-org/gomatrix" v0.0.0-20171003113848-a7fc80c8060c
- "github.com/mattn/go-runewidth" v0.0.2
- "github.com/nsf/termbox-go" v0.0.0-20180303152453-e2050e41c884
- "github.com/rivo/tview" v0.0.0-20180313071706-0b69b9b58142
- "golang.org/x/text" v0.0.0-20171214130843-f21a4dfb5e38
- "gopkg.in/yaml.v2" v1.1.1-gopkgin-v2.1.1
-)
diff --git a/gomuks.go b/gomuks.go
index 5fa0a8a..fecb67a 100644
--- a/gomuks.go
+++ b/gomuks.go
@@ -21,43 +21,37 @@ import (
"path/filepath"
"maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/config"
+ "maunium.net/go/gomuks/interface"
+ "maunium.net/go/gomuks/matrix"
+ "maunium.net/go/gomuks/ui"
+ "maunium.net/go/gomuks/ui/debug"
"maunium.net/go/tview"
)
-type Gomuks interface {
- Debug() DebugPrinter
- Matrix() *gomatrix.Client
- MatrixContainer() *MatrixContainer
- App() *tview.Application
- UI() *GomuksUI
- Config() *Config
-
- Start()
- Stop()
- Recover()
-}
-
type gomuks struct {
app *tview.Application
- ui *GomuksUI
- matrix *MatrixContainer
- debug *DebugPane
- config *Config
+ ui *ui.GomuksUI
+ matrix *matrix.Container
+ debug *debug.Pane
+ config *config.Config
}
-var gdebug DebugPrinter
-
-func NewGomuks(debug bool) *gomuks {
+func NewGomuks(enableDebug bool) *gomuks {
configDir := filepath.Join(os.Getenv("HOME"), ".config/gomuks")
gmx := &gomuks{
app: tview.NewApplication(),
}
- gmx.debug = NewDebugPane(gmx)
- gdebug = gmx.debug
- gmx.config = NewConfig(gmx, configDir)
- gmx.ui = NewGomuksUI(gmx)
- gmx.matrix = NewMatrixContainer(gmx)
- gmx.ui.matrix = gmx.matrix
+
+ gmx.debug = debug.NewPane()
+ gmx.debug.SetChangedFunc(func() {
+ gmx.ui.Render()
+ })
+ debug.Default = gmx.debug
+
+ gmx.config = config.NewConfig(configDir)
+ gmx.ui = ui.NewGomuksUI(gmx)
+ gmx.matrix = matrix.NewMatrixContainer(gmx)
gmx.config.Load()
if len(gmx.config.MXID) > 0 {
@@ -67,7 +61,7 @@ func NewGomuks(debug bool) *gomuks {
gmx.matrix.InitClient()
main := gmx.ui.InitViews()
- if debug {
+ if enableDebug {
main = gmx.debug.Wrap(main)
}
gmx.app.SetRoot(main, true)
@@ -101,15 +95,11 @@ func (gmx *gomuks) Start() {
}
}
-func (gmx *gomuks) Debug() DebugPrinter {
- return gmx.debug
-}
-
func (gmx *gomuks) Matrix() *gomatrix.Client {
- return gmx.matrix.client
+ return gmx.matrix.Client()
}
-func (gmx *gomuks) MatrixContainer() *MatrixContainer {
+func (gmx *gomuks) MatrixContainer() ifc.MatrixContainer {
return gmx.matrix
}
@@ -117,11 +107,11 @@ func (gmx *gomuks) App() *tview.Application {
return gmx.app
}
-func (gmx *gomuks) Config() *Config {
+func (gmx *gomuks) Config() *config.Config {
return gmx.config
}
-func (gmx *gomuks) UI() *GomuksUI {
+func (gmx *gomuks) UI() ifc.GomuksUI {
return gmx.ui
}
diff --git a/interface/gomuks.go b/interface/gomuks.go
new file mode 100644
index 0000000..b90aa88
--- /dev/null
+++ b/interface/gomuks.go
@@ -0,0 +1,35 @@
+// 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 ifc
+
+import (
+ "maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/config"
+ "maunium.net/go/tview"
+)
+
+type Gomuks interface {
+ Matrix() *gomatrix.Client
+ MatrixContainer() MatrixContainer
+ App() *tview.Application
+ UI() GomuksUI
+ Config() *config.Config
+
+ Start()
+ Stop()
+ Recover()
+}
diff --git a/interface/matrix.go b/interface/matrix.go
new file mode 100644
index 0000000..4c30b5e
--- /dev/null
+++ b/interface/matrix.go
@@ -0,0 +1,40 @@
+// 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 ifc
+
+import (
+ "maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/matrix/room"
+)
+
+type MatrixContainer interface {
+ Client() *gomatrix.Client
+ InitClient() error
+ Initialized() bool
+ Login(user, password string) error
+ Start()
+ Stop()
+ // HandleMessage(evt *gomatrix.Event)
+ // HandleMembership(evt *gomatrix.Event)
+ // HandleTyping(evt *gomatrix.Event)
+ SendMessage(roomID, message string)
+ SendTyping(roomID string, typing bool)
+ JoinRoom(roomID string) error
+ LeaveRoom(roomID string) error
+ GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error)
+ GetRoom(roomID string) *room.Room
+}
diff --git a/interface/ui.go b/interface/ui.go
new file mode 100644
index 0000000..406aa2f
--- /dev/null
+++ b/interface/ui.go
@@ -0,0 +1,54 @@
+// 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 ifc
+
+import (
+ "maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/ui/types"
+ "maunium.net/go/gomuks/ui/widget"
+ "maunium.net/go/tview"
+)
+
+type View string
+
+// Allowed views in GomuksUI
+const (
+ ViewLogin View = "login"
+ ViewMain View = "main"
+)
+
+type GomuksUI interface {
+ Render()
+ SetView(name View)
+ InitViews() tview.Primitive
+ MainView() MainView
+}
+
+type MainView interface {
+ InputTabComplete(text string, cursorOffset int) string
+ GetRoom(roomID string) *widget.RoomView
+ HasRoom(roomID string) bool
+ AddRoom(roomID string)
+ RemoveRoom(roomID string)
+ SetRooms(roomIDs []string)
+
+ SetTyping(roomID string, users []string)
+ AddServiceMessage(roomID string, message string)
+ GetHistory(room string)
+ ProcessMessageEvent(evt *gomatrix.Event) (*widget.RoomView, *types.Message)
+ ProcessMembershipEvent(evt *gomatrix.Event, new bool) (*widget.RoomView, *types.Message)
+}
diff --git a/matrix.go b/matrix/matrix.go
index ea1c5c6..7652188 100644
--- a/matrix.go
+++ b/matrix/matrix.go
@@ -14,7 +14,7 @@
// 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 main
+package matrix
import (
"fmt"
@@ -22,24 +22,27 @@ import (
"time"
"maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/config"
+ "maunium.net/go/gomuks/interface"
+ rooms "maunium.net/go/gomuks/matrix/room"
+ "maunium.net/go/gomuks/ui/debug"
+ "maunium.net/go/gomuks/ui/widget"
)
-type MatrixContainer struct {
+type Container struct {
client *gomatrix.Client
- gmx Gomuks
- ui *GomuksUI
- debug DebugPrinter
- config *Config
+ gmx ifc.Gomuks
+ ui ifc.GomuksUI
+ config *config.Config
running bool
stop chan bool
typing int64
}
-func NewMatrixContainer(gmx Gomuks) *MatrixContainer {
- c := &MatrixContainer{
+func NewMatrixContainer(gmx ifc.Gomuks) *Container {
+ c := &Container{
config: gmx.Config(),
- debug: gmx.Debug(),
ui: gmx.UI(),
gmx: gmx,
}
@@ -47,7 +50,7 @@ func NewMatrixContainer(gmx Gomuks) *MatrixContainer {
return c
}
-func (c *MatrixContainer) InitClient() error {
+func (c *Container) InitClient() error {
if len(c.config.HS) == 0 {
return fmt.Errorf("no homeserver in config")
}
@@ -77,11 +80,11 @@ func (c *MatrixContainer) InitClient() error {
return nil
}
-func (c *MatrixContainer) Initialized() bool {
+func (c *Container) Initialized() bool {
return c.client != nil
}
-func (c *MatrixContainer) Login(user, password string) error {
+func (c *Container) Login(user, password string) error {
resp, err := c.client.Login(&gomatrix.ReqLogin{
Type: "m.login.password",
User: user,
@@ -103,24 +106,28 @@ func (c *MatrixContainer) Login(user, password string) error {
return nil
}
-func (c *MatrixContainer) Stop() {
+func (c *Container) Stop() {
if c.running {
c.stop <- true
c.client.StopSync()
}
}
-func (c *MatrixContainer) UpdateRoomList() {
- rooms, err := c.client.JoinedRooms()
+func (c *Container) Client() *gomatrix.Client {
+ return c.client
+}
+
+func (c *Container) UpdateRoomList() {
+ resp, err := c.client.JoinedRooms()
if err != nil {
- c.debug.Print("Error fetching room list:", err)
+ debug.Print("Error fetching room list:", err)
return
}
- c.ui.MainView().SetRoomList(rooms.JoinedRooms)
+ c.ui.MainView().SetRooms(resp.JoinedRooms)
}
-func (c *MatrixContainer) OnLogin() {
+func (c *Container) OnLogin() {
c.client.Store = c.config.Session
syncer := NewGomuksSyncer(c.config.Session)
@@ -132,38 +139,38 @@ func (c *MatrixContainer) OnLogin() {
c.UpdateRoomList()
}
-func (c *MatrixContainer) Start() {
+func (c *Container) Start() {
defer c.gmx.Recover()
+ c.ui.SetView(ifc.ViewMain)
c.OnLogin()
- c.debug.Print("Starting sync...")
+ debug.Print("Starting sync...")
c.running = true
- c.ui.SetView(ViewMain)
for {
select {
case <-c.stop:
- c.debug.Print("Stopping sync...")
+ debug.Print("Stopping sync...")
c.running = false
return
default:
if err := c.client.Sync(); err != nil {
- c.debug.Print("Sync() errored", err)
+ debug.Print("Sync() errored", err)
} else {
- c.debug.Print("Sync() returned without error")
+ debug.Print("Sync() returned without error")
}
}
}
}
-func (c *MatrixContainer) HandleMessage(evt *gomatrix.Event) {
+func (c *Container) HandleMessage(evt *gomatrix.Event) {
room, message := c.ui.MainView().ProcessMessageEvent(evt)
if room != nil {
- room.AddMessage(message, AppendMessage)
+ room.AddMessage(message, widget.AppendMessage)
}
}
-func (c *MatrixContainer) HandleMembership(evt *gomatrix.Event) {
+func (c *Container) HandleMembership(evt *gomatrix.Event) {
const Hour = 1 * 60 * 60 * 1000
if evt.Unsigned.Age > Hour {
return
@@ -172,15 +179,15 @@ func (c *MatrixContainer) HandleMembership(evt *gomatrix.Event) {
room, message := c.ui.MainView().ProcessMembershipEvent(evt, true)
if room != nil {
// TODO this shouldn't be necessary
- room.room.UpdateState(evt)
+ room.Room.UpdateState(evt)
// TODO This should probably also be in a different place
room.UpdateUserList()
- room.AddMessage(message, AppendMessage)
+ room.AddMessage(message, widget.AppendMessage)
}
}
-func (c *MatrixContainer) HandleTyping(evt *gomatrix.Event) {
+func (c *Container) HandleTyping(evt *gomatrix.Event) {
users := evt.Content["user_ids"].([]interface{})
strUsers := make([]string, len(users))
@@ -190,29 +197,29 @@ func (c *MatrixContainer) HandleTyping(evt *gomatrix.Event) {
c.ui.MainView().SetTyping(evt.RoomID, strUsers)
}
-func (c *MatrixContainer) SendMessage(roomID, message string) {
+func (c *Container) SendMessage(roomID, message string) {
c.gmx.Recover()
c.SendTyping(roomID, false)
c.client.SendText(roomID, message)
}
-func (c *MatrixContainer) SendTyping(roomID string, typing bool) {
+func (c *Container) SendTyping(roomID string, typing bool) {
c.gmx.Recover()
- time := time.Now().Unix()
- if c.typing > time && typing {
+ ts := time.Now().Unix()
+ if c.typing > ts && typing {
return
}
if typing {
c.client.UserTyping(roomID, true, 5000)
- c.typing = time + 5
+ c.typing = ts + 5
} else {
c.client.UserTyping(roomID, false, 0)
c.typing = 0
}
}
-func (c *MatrixContainer) JoinRoom(roomID string) error {
+func (c *Container) JoinRoom(roomID string) error {
if len(roomID) == 0 {
return fmt.Errorf("invalid room ID")
}
@@ -222,26 +229,40 @@ func (c *MatrixContainer) JoinRoom(roomID string) error {
server = roomID[strings.Index(roomID, ":")+1:]
}
- resp, err := c.client.JoinRoom(roomID, server, nil)
+ _, err := c.client.JoinRoom(roomID, server, nil)
+ if err != nil {
+ return err
+ }
+
+ // TODO probably safe to remove
+ // c.ui.MainView().AddRoom(resp.RoomID)
+ return nil
+}
+
+func (c *Container) LeaveRoom(roomID string) error {
+ if len(roomID) == 0 {
+ return fmt.Errorf("invalid room ID")
+ }
+
+ _, err := c.client.LeaveRoom(roomID)
if err != nil {
return err
}
- c.ui.MainView().AddRoom(resp.RoomID)
return nil
}
-func (c *MatrixContainer) getState(roomID string) []*gomatrix.Event {
+func (c *Container) getState(roomID string) []*gomatrix.Event {
content := make([]*gomatrix.Event, 0)
err := c.client.StateEvent(roomID, "", "", &content)
if err != nil {
- c.debug.Print("Error getting state of", roomID, err)
+ debug.Print("Error getting state of", roomID, err)
return nil
}
return content
}
-func (c *MatrixContainer) GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error) {
+func (c *Container) GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error) {
resp, err := c.client.Messages(roomID, prevBatch, "", 'b', limit)
if err != nil {
return nil, "", err
@@ -249,7 +270,7 @@ func (c *MatrixContainer) GetHistory(roomID, prevBatch string, limit int) ([]gom
return resp.Chunk, resp.End, nil
}
-func (c *MatrixContainer) GetRoom(roomID string) *Room {
+func (c *Container) GetRoom(roomID string) *rooms.Room {
room := c.config.Session.GetRoom(roomID)
if room != nil && len(room.State) == 0 {
events := c.getState(room.ID)
diff --git a/matrix/room/member.go b/matrix/room/member.go
new file mode 100644
index 0000000..474d2fd
--- /dev/null
+++ b/matrix/room/member.go
@@ -0,0 +1,51 @@
+// 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 room
+
+import (
+ "maunium.net/go/gomatrix"
+)
+
+type Member struct {
+ UserID string `json:"-"`
+ Membership string `json:"membership"`
+ DisplayName string `json:"displayname"`
+ AvatarURL string `json:"avatar_url"`
+}
+
+func eventToRoomMember(userID string, event *gomatrix.Event) *Member {
+ if event == nil {
+ return &Member{
+ UserID: userID,
+ Membership: "leave",
+ }
+ }
+ membership, _ := event.Content["membership"].(string)
+ avatarURL, _ := event.Content["avatar_url"].(string)
+
+ displayName, _ := event.Content["displayname"].(string)
+ if len(displayName) == 0 {
+ displayName = userID
+ }
+
+ return &Member{
+ UserID: userID,
+ Membership: membership,
+ DisplayName: displayName,
+ AvatarURL: avatarURL,
+ }
+}
diff --git a/room.go b/matrix/room/room.go
index 19c6865..4b3bda2 100644
--- a/room.go
+++ b/matrix/room/room.go
@@ -14,7 +14,7 @@
// 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 main
+package room
import (
"maunium.net/go/gomatrix"
@@ -25,7 +25,7 @@ type Room struct {
*gomatrix.Room
PrevBatch string
- memberCache map[string]*RoomMember
+ memberCache map[string]*Member
nameCache string
topicCache string
}
@@ -107,38 +107,8 @@ func (room *Room) GetTitle() string {
return room.nameCache
}
-type RoomMember struct {
- UserID string `json:"-"`
- Membership string `json:"membership"`
- DisplayName string `json:"displayname"`
- AvatarURL string `json:"avatar_url"`
-}
-
-func eventToRoomMember(userID string, event *gomatrix.Event) *RoomMember {
- if event == nil {
- return &RoomMember{
- UserID: userID,
- Membership: "leave",
- }
- }
- membership, _ := event.Content["membership"].(string)
- avatarURL, _ := event.Content["avatar_url"].(string)
-
- displayName, _ := event.Content["displayname"].(string)
- if len(displayName) == 0 {
- displayName = userID
- }
-
- return &RoomMember{
- UserID: userID,
- Membership: membership,
- DisplayName: displayName,
- AvatarURL: avatarURL,
- }
-}
-
-func (room *Room) createMemberCache() map[string]*RoomMember {
- cache := make(map[string]*RoomMember)
+func (room *Room) createMemberCache() map[string]*Member {
+ cache := make(map[string]*Member)
events := room.GetStateEvents("m.room.member")
if events != nil {
for userID, event := range events {
@@ -152,14 +122,14 @@ func (room *Room) createMemberCache() map[string]*RoomMember {
return cache
}
-func (room *Room) GetMembers() map[string]*RoomMember {
+func (room *Room) GetMembers() map[string]*Member {
if len(room.memberCache) == 0 {
room.createMemberCache()
}
return room.memberCache
}
-func (room *Room) GetMember(userID string) *RoomMember {
+func (room *Room) GetMember(userID string) *Member {
if len(room.memberCache) == 0 {
room.createMemberCache()
}
diff --git a/sync.go b/matrix/sync.go
index 2e0bbcf..ab5d047 100644
--- a/sync.go
+++ b/matrix/sync.go
@@ -1,4 +1,4 @@
-package main
+package matrix
import (
"encoding/json"
@@ -7,20 +7,21 @@ import (
"time"
"maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/config"
)
// GomuksSyncer is the default syncing implementation. You can either write your own syncer, or selectively
// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
// pattern to notify callers about incoming events. See GomuksSyncer.OnEventType for more information.
type GomuksSyncer struct {
- Session *Session
+ Session *config.Session
listeners map[string][]gomatrix.OnEventListener // event type to listeners array
}
// NewGomuksSyncer returns an instantiated GomuksSyncer
-func NewGomuksSyncer(session *Session) *GomuksSyncer {
+func NewGomuksSyncer(session *config.Session) *GomuksSyncer {
return &GomuksSyncer{
- Session: session,
+ Session: session,
listeners: make(map[string][]gomatrix.OnEventListener),
}
}
@@ -38,22 +39,22 @@ func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (er
}()
for _, event := range res.Presence.Events {
- s.notifyListeners(&event)
+ s.notifyListeners(event)
}
for roomID, roomData := range res.Rooms.Join {
room := s.Session.GetRoom(roomID)
for _, event := range roomData.State.Events {
event.RoomID = roomID
- room.UpdateState(&event)
- s.notifyListeners(&event)
+ room.UpdateState(event)
+ s.notifyListeners(event)
}
for _, event := range roomData.Timeline.Events {
event.RoomID = roomID
- s.notifyListeners(&event)
+ s.notifyListeners(event)
}
for _, event := range roomData.Ephemeral.Events {
event.RoomID = roomID
- s.notifyListeners(&event)
+ s.notifyListeners(event)
}
if len(room.PrevBatch) == 0 {
@@ -64,8 +65,8 @@ func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (er
room := s.Session.GetRoom(roomID)
for _, event := range roomData.State.Events {
event.RoomID = roomID
- room.UpdateState(&event)
- s.notifyListeners(&event)
+ room.UpdateState(event)
+ s.notifyListeners(event)
}
}
for roomID, roomData := range res.Rooms.Leave {
@@ -73,8 +74,8 @@ func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (er
for _, event := range roomData.Timeline.Events {
if event.StateKey != nil {
event.RoomID = roomID
- room.UpdateState(&event)
- s.notifyListeners(&event)
+ room.UpdateState(event)
+ s.notifyListeners(event)
}
}
diff --git a/debug.go b/ui/debug/debug.go
index 15aac60..c855897 100644
--- a/debug.go
+++ b/ui/debug/debug.go
@@ -14,7 +14,7 @@
// 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 main
+package debug
import (
"fmt"
@@ -22,54 +22,62 @@ import (
"maunium.net/go/tview"
)
-const DebugPaneHeight = 35
-
-type DebugPrinter interface {
+type Printer interface {
Printf(text string, args ...interface{})
Print(text ...interface{})
}
-type DebugPane struct {
- pane *tview.TextView
+type Pane struct {
+ *tview.TextView
+ Height int
num int
- gmx Gomuks
}
-func NewDebugPane(gmx Gomuks) *DebugPane {
+var Default Printer
+
+func NewPane() *Pane {
pane := tview.NewTextView()
pane.
SetScrollable(true).
- SetWrap(true)
- pane.SetChangedFunc(func() {
- gmx.App().Draw()
- })
- pane.SetBorder(true).SetTitle("Debug output")
+ SetWrap(true).
+ SetBorder(true).
+ SetTitle("Debug output")
fmt.Fprintln(pane, "[0] Debug pane initialized")
- return &DebugPane{
- pane: pane,
+ return &Pane{
+ TextView: pane,
+ Height: 35,
num: 0,
- gmx: gmx,
}
}
-func (db *DebugPane) Printf(text string, args ...interface{}) {
- db.Write(fmt.Sprintf(text, args...) + "\n")
+func (db *Pane) Printf(text string, args ...interface{}) {
+ db.WriteString(fmt.Sprintf(text, args...) + "\n")
}
-func (db *DebugPane) Print(text ...interface{}) {
- db.Write(fmt.Sprintln(text...))
+func (db *Pane) Print(text ...interface{}) {
+ db.WriteString(fmt.Sprintln(text...))
}
-func (db *DebugPane) Write(text string) {
- if db.pane != nil {
- db.num++
- fmt.Fprintf(db.pane, "[%d] %s", db.num, text)
- }
+func (db *Pane) WriteString(text string) {
+ db.num++
+ fmt.Fprintf(db, "[%d] %s", db.num, text)
}
-func (db *DebugPane) Wrap(main tview.Primitive) tview.Primitive {
- return tview.NewGrid().SetRows(0, DebugPaneHeight).SetColumns(0).
+func (db *Pane) Wrap(main tview.Primitive) tview.Primitive {
+ return tview.NewGrid().SetRows(0, db.Height).SetColumns(0).
AddItem(main, 0, 0, 1, 1, 1, 1, true).
- AddItem(db.pane, 1, 0, 1, 1, 1, 1, false)
+ AddItem(db, 1, 0, 1, 1, 1, 1, false)
+}
+
+func Printf(text string, args ...interface{}) {
+ if Default != nil {
+ Default.Printf(text, args...)
+ }
+}
+
+func Print(text ...interface{}) {
+ if Default != nil {
+ Default.Print(text...)
+ }
}
diff --git a/ui/types/message.go b/ui/types/message.go
new file mode 100644
index 0000000..b69eab2
--- /dev/null
+++ b/ui/types/message.go
@@ -0,0 +1,85 @@
+// 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 (
+ "regexp"
+ "strings"
+
+ "github.com/gdamore/tcell"
+ "github.com/mattn/go-runewidth"
+)
+
+type Message struct {
+ ID string
+ Sender string
+ Text string
+ Timestamp string
+ Date string
+
+ Buffer []string
+ SenderColor tcell.Color
+}
+
+func NewMessage(id, sender, text, timestamp, date string, senderColor tcell.Color) *Message {
+ return &Message{
+ ID: id,
+ Sender: sender,
+ Text: text,
+ Timestamp: timestamp,
+ Date: date,
+ SenderColor: senderColor,
+ }
+}
+
+var (
+ boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
+ spacePattern = regexp.MustCompile(`\s+`)
+)
+
+func (message *Message) CalculateBuffer(width int) {
+ if width < 1 {
+ return
+ }
+ message.Buffer = []string{}
+ forcedLinebreaks := strings.Split(message.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):]
+ }
+ }
+}
diff --git a/ui.go b/ui/ui.go
index 96b4e41..eab7642 100644
--- a/ui.go
+++ b/ui/ui.go
@@ -14,25 +14,17 @@
// 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 main
+package ui
import (
"github.com/gdamore/tcell"
+ "maunium.net/go/gomuks/interface"
"maunium.net/go/tview"
)
-// Allowed views in GomuksUI
-const (
- ViewLogin = "login"
- ViewMain = "main"
-)
-
type GomuksUI struct {
- gmx Gomuks
+ gmx ifc.Gomuks
app *tview.Application
- matrix *MatrixContainer
- debug DebugPrinter
- config *Config
views *tview.Pages
mainView *MainView
@@ -44,13 +36,10 @@ func init() {
tview.Styles.ContrastBackgroundColor = tcell.ColorDarkGreen
}
-func NewGomuksUI(gmx Gomuks) (ui *GomuksUI) {
+func NewGomuksUI(gmx ifc.Gomuks) (ui *GomuksUI) {
ui = &GomuksUI{
gmx: gmx,
app: gmx.App(),
- matrix: gmx.MatrixContainer(),
- debug: gmx.Debug(),
- config: gmx.Config(),
views: tview.NewPages(),
}
ui.views.SetChangedFunc(ui.Render)
@@ -61,16 +50,16 @@ func (ui *GomuksUI) Render() {
ui.app.Draw()
}
-func (ui *GomuksUI) SetView(name string) {
- ui.views.SwitchToPage(name)
+func (ui *GomuksUI) SetView(name ifc.View) {
+ ui.views.SwitchToPage(string(name))
}
func (ui *GomuksUI) InitViews() tview.Primitive {
- ui.views.AddPage(ViewLogin, ui.NewLoginView(), true, true)
- ui.views.AddPage(ViewMain, ui.NewMainView(), true, false)
+ ui.views.AddPage(string(ifc.ViewLogin), ui.NewLoginView(), true, true)
+ ui.views.AddPage(string(ifc.ViewMain), ui.NewMainView(), true, false)
return ui.views
}
-func (ui *GomuksUI) MainView() *MainView {
+func (ui *GomuksUI) MainView() ifc.MainView {
return ui.mainView
}
diff --git a/view-login.go b/ui/view-login.go
index 0c18fbc..2a19d3b 100644
--- a/view-login.go
+++ b/ui/view-login.go
@@ -14,14 +14,16 @@
// 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 main
+package ui
import (
+ "maunium.net/go/gomuks/ui/debug"
+ "maunium.net/go/gomuks/ui/widget"
"maunium.net/go/tview"
)
func (ui *GomuksUI) NewLoginView() tview.Primitive {
- hs := ui.config.HS
+ hs := ui.gmx.Config().HS
if len(hs) == 0 {
hs = "https://matrix.org"
}
@@ -29,13 +31,13 @@ func (ui *GomuksUI) NewLoginView() tview.Primitive {
ui.loginView = tview.NewForm()
ui.loginView.
AddInputField("Homeserver", hs, 30, nil, nil).
- AddInputField("Username", ui.config.MXID, 30, nil, nil).
+ AddInputField("Username", ui.gmx.Config().MXID, 30, nil, nil).
AddPasswordField("Password", "", 30, '*', nil).
AddButton("Log in", ui.login).
AddButton("Quit", ui.gmx.Stop).
SetButtonsAlign(tview.AlignCenter).
SetBorder(true).SetTitle("Log in to Matrix")
- return Center(45, 11, ui.loginView)
+ return widget.Center(45, 11, ui.loginView)
}
func (ui *GomuksUI) login() {
@@ -43,8 +45,9 @@ func (ui *GomuksUI) login() {
mxid := ui.loginView.GetFormItem(1).(*tview.InputField).GetText()
password := ui.loginView.GetFormItem(2).(*tview.InputField).GetText()
- ui.debug.Printf("Logging into %s as %s...", hs, mxid)
- ui.config.HS = hs
- ui.debug.Print("Connect result:", ui.matrix.InitClient())
- ui.debug.Print("Login result:", ui.matrix.Login(mxid, password))
+ debug.Printf("Logging into %s as %s...", hs, mxid)
+ ui.gmx.Config().HS = hs
+ mx := ui.gmx.MatrixContainer()
+ debug.Print("Connect result:", mx.InitClient())
+ debug.Print("Login result:", mx.Login(mxid, password))
}
diff --git a/view-main.go b/ui/view-main.go
index 2fd503a..8ec482e 100644
--- a/view-main.go
+++ b/ui/view-main.go
@@ -14,7 +14,7 @@
// 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 main
+package ui
import (
"fmt"
@@ -26,6 +26,11 @@ import (
"github.com/gdamore/tcell"
"github.com/mattn/go-runewidth"
"maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/config"
+ "maunium.net/go/gomuks/interface"
+ "maunium.net/go/gomuks/ui/debug"
+ "maunium.net/go/gomuks/ui/types"
+ "maunium.net/go/gomuks/ui/widget"
"maunium.net/go/tview"
)
@@ -34,15 +39,14 @@ type MainView struct {
roomList *tview.List
roomView *tview.Pages
- rooms map[string]*RoomView
- input *AdvancedInputField
+ rooms map[string]*widget.RoomView
+ input *widget.AdvancedInputField
currentRoomIndex int
roomIDs []string
- matrix *MatrixContainer
- debug DebugPrinter
- gmx Gomuks
- config *Config
+ matrix ifc.MatrixContainer
+ gmx ifc.Gomuks
+ config *config.Config
parent *GomuksUI
}
@@ -55,13 +59,12 @@ func (ui *GomuksUI) NewMainView() tview.Primitive {
Grid: tview.NewGrid(),
roomList: tview.NewList(),
roomView: tview.NewPages(),
- rooms: make(map[string]*RoomView),
- input: NewAdvancedInputField(),
+ rooms: make(map[string]*widget.RoomView),
+ input: widget.NewAdvancedInputField(),
- matrix: ui.matrix,
- debug: ui.debug,
+ matrix: ui.gmx.MatrixContainer(),
gmx: ui.gmx,
- config: ui.config,
+ config: ui.gmx.Config(),
parent: ui,
}
@@ -83,7 +86,7 @@ func (ui *GomuksUI) NewMainView() tview.Primitive {
SetInputCapture(mainView.InputCapture)
mainView.addItem(mainView.roomList, 0, 0, 2, 1)
- mainView.addItem(NewBorder(), 0, 1, 2, 1)
+ mainView.addItem(widget.NewBorder(), 0, 1, 2, 1)
mainView.addItem(mainView.roomView, 0, 2, 1, 1)
mainView.AddItem(mainView.input, 1, 2, 1, 1, 0, 0, true)
@@ -121,8 +124,7 @@ func (view *MainView) InputTabComplete(text string, cursorOffset int) string {
if len(userCompletions) == 1 {
text = str[0:len(str)-len(word)] + userCompletions[0] + text[len(str):]
} else if len(userCompletions) > 1 && len(userCompletions) < 6 {
- roomView.status.Clear()
- fmt.Fprintf(roomView.status, "Completions: %s", strings.Join(userCompletions, ", "))
+ roomView.SetStatus(fmt.Sprintf("Completions: %s", strings.Join(userCompletions, ", ")))
}
}
return text
@@ -147,7 +149,7 @@ func (view *MainView) InputDone(key tcell.Key) {
func (view *MainView) HandleCommand(room, command string, args []string) {
view.gmx.Recover()
- view.debug.Print("Handling command", command, args)
+ debug.Print("Handling command", command, args)
switch command {
case "/quit":
view.gmx.Stop()
@@ -157,13 +159,13 @@ func (view *MainView) HandleCommand(room, command string, args []string) {
case "/part":
fallthrough
case "/leave":
- view.matrix.client.LeaveRoom(room)
+ debug.Print(view.matrix.LeaveRoom(room))
case "/join":
if len(args) == 0 {
view.AddServiceMessage(room, "Usage: /join <room>")
break
}
- view.debug.Print(view.matrix.JoinRoom(args[0]))
+ debug.Print(view.matrix.JoinRoom(args[0]))
default:
view.AddServiceMessage(room, "Unknown command.")
}
@@ -218,7 +220,7 @@ func (view *MainView) addRoom(index int, room string) {
view.SwitchRoom(index)
})
if !view.roomView.HasPage(room) {
- roomView := NewRoomView(view, roomStore)
+ roomView := widget.NewRoomView(view, roomStore)
view.rooms[room] = roomView
view.roomView.AddPage(room, roomView, true, false)
roomView.UpdateUserList()
@@ -226,7 +228,7 @@ func (view *MainView) addRoom(index int, room string) {
}
}
-func (view *MainView) GetRoom(id string) *RoomView {
+func (view *MainView) GetRoom(id string) *widget.RoomView {
return view.rooms[id]
}
@@ -265,11 +267,11 @@ func (view *MainView) RemoveRoom(room string) {
view.Render()
}
-func (view *MainView) SetRoomList(rooms []string) {
+func (view *MainView) SetRooms(rooms []string) {
view.roomIDs = rooms
view.roomList.Clear()
view.roomView.Clear()
- view.rooms = make(map[string]*RoomView)
+ view.rooms = make(map[string]*widget.RoomView)
for index, room := range rooms {
view.addRoom(index, room)
}
@@ -289,7 +291,7 @@ func (view *MainView) AddServiceMessage(room, message string) {
if ok {
messageView := roomView.MessageView()
message := messageView.NewMessage("", "*", message, time.Now())
- messageView.AddMessage(message, AppendMessage)
+ messageView.AddMessage(message, widget.AppendMessage)
view.parent.Render()
}
}
@@ -300,29 +302,29 @@ func (view *MainView) Render() {
func (view *MainView) GetHistory(room string) {
roomView := view.rooms[room]
- history, _, err := view.matrix.GetHistory(roomView.room.ID, view.config.Session.NextBatch, 50)
+ history, _, err := view.matrix.GetHistory(roomView.Room.ID, view.config.Session.NextBatch, 50)
if err != nil {
- view.debug.Print("Failed to fetch history for", roomView.room.ID, err)
+ debug.Print("Failed to fetch history for", roomView.Room.ID, err)
return
}
for _, evt := range history {
- var room *RoomView
- var message *Message
+ var room *widget.RoomView
+ var message *types.Message
if evt.Type == "m.room.message" {
room, message = view.ProcessMessageEvent(&evt)
} else if evt.Type == "m.room.member" {
room, message = view.ProcessMembershipEvent(&evt, false)
}
if room != nil && message != nil {
- room.AddMessage(message, PrependMessage)
+ room.AddMessage(message, widget.PrependMessage)
}
}
}
-func (view *MainView) ProcessMessageEvent(evt *gomatrix.Event) (room *RoomView, message *Message) {
+func (view *MainView) ProcessMessageEvent(evt *gomatrix.Event) (room *widget.RoomView, message *types.Message) {
room = view.GetRoom(evt.RoomID)
if room != nil {
- text := evt.Content["body"].(string)
+ text, _ := evt.Content["body"].(string)
message = room.NewMessage(evt.ID, evt.Sender, text, unixToTime(evt.Timestamp))
}
return
@@ -344,7 +346,7 @@ func (view *MainView) processOwnMembershipChange(evt *gomatrix.Event) {
}
}
-func (view *MainView) ProcessMembershipEvent(evt *gomatrix.Event, new bool) (room *RoomView, message *Message) {
+func (view *MainView) ProcessMembershipEvent(evt *gomatrix.Event, new bool) (room *widget.RoomView, message *types.Message) {
if new && evt.StateKey != nil && *evt.StateKey == view.config.Session.MXID {
view.processOwnMembershipChange(evt)
}
diff --git a/advanced-inputfield.go b/ui/widget/advanced-inputfield.go
index 8b5b47a..6928c27 100644
--- a/advanced-inputfield.go
+++ b/ui/widget/advanced-inputfield.go
@@ -14,7 +14,7 @@
// 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 main
+package widget
import (
"math"
diff --git a/border.go b/ui/widget/border.go
index cd0b8a1..a32f4dd 100644
--- a/border.go
+++ b/ui/widget/border.go
@@ -14,7 +14,7 @@
// 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 main
+package widget
import (
"github.com/gdamore/tcell"
diff --git a/ui/widget/center.go b/ui/widget/center.go
new file mode 100644
index 0000000..41181a2
--- /dev/null
+++ b/ui/widget/center.go
@@ -0,0 +1,32 @@
+// 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 widget
+
+import (
+ "maunium.net/go/tview"
+)
+
+func Center(width, height int, p tview.Primitive) tview.Primitive {
+ return tview.NewFlex().
+ AddItem(tview.NewBox(), 0, 1, false).
+ AddItem(tview.NewFlex().
+ SetDirection(tview.FlexRow).
+ AddItem(tview.NewBox(), 0, 1, false).
+ AddItem(p, height, 1, true).
+ AddItem(tview.NewBox(), 0, 1, false), width, 1, true).
+ AddItem(tview.NewBox(), 0, 1, false)
+}
diff --git a/ui/widget/color.go b/ui/widget/color.go
new file mode 100644
index 0000000..874b93d
--- /dev/null
+++ b/ui/widget/color.go
@@ -0,0 +1,60 @@
+// 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 widget
+
+import (
+ "fmt"
+ "hash/fnv"
+ "sort"
+
+ "github.com/gdamore/tcell"
+)
+
+var colorNames []string
+
+func init() {
+ colorNames = make([]string, len(tcell.ColorNames))
+ i := 0
+ for name, _ := range tcell.ColorNames {
+ colorNames[i] = name
+ i++
+ }
+ sort.Sort(sort.StringSlice(colorNames))
+}
+
+func GetHashColorName(s string) string {
+ switch s {
+ case "-->":
+ return "green"
+ case "<--":
+ return "red"
+ case "---":
+ return "yellow"
+ default:
+ h := fnv.New32a()
+ h.Write([]byte(s))
+ return colorNames[int(h.Sum32())%len(colorNames)]
+ }
+}
+
+func GetHashColor(s string) tcell.Color {
+ return tcell.ColorNames[GetHashColorName(s)]
+}
+
+func AddHashColor(s string) string {
+ return fmt.Sprintf("[%s]%s[white]", GetHashColorName(s), s)
+}
diff --git a/uiutil.go b/ui/widget/form-text-view.go
index 0ba37ef..58046e9 100644
--- a/uiutil.go
+++ b/ui/widget/form-text-view.go
@@ -14,24 +14,13 @@
// 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 main
+package widget
import (
"github.com/gdamore/tcell"
"maunium.net/go/tview"
)
-func Center(width, height int, p tview.Primitive) tview.Primitive {
- return tview.NewFlex().
- AddItem(tview.NewBox(), 0, 1, false).
- AddItem(tview.NewFlex().
- SetDirection(tview.FlexRow).
- AddItem(tview.NewBox(), 0, 1, false).
- AddItem(p, height, 1, true).
- AddItem(tview.NewBox(), 0, 1, false), width, 1, true).
- AddItem(tview.NewBox(), 0, 1, false)
-}
-
type FormTextView struct {
*tview.TextView
}
diff --git a/message-view.go b/ui/widget/message-view.go
index d3d2df9..3503a5f 100644
--- a/message-view.go
+++ b/ui/widget/message-view.go
@@ -14,79 +14,19 @@
// 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 main
+package widget
import (
"fmt"
- "regexp"
"strings"
"time"
"github.com/gdamore/tcell"
"github.com/mattn/go-runewidth"
+ "maunium.net/go/gomuks/ui/types"
"maunium.net/go/tview"
)
-type Message struct {
- ID string
- Sender string
- Text string
- Timestamp string
- Date string
-
- buffer []string
- senderColor tcell.Color
-}
-
-func NewMessage(id, sender, text, timestamp, date string, senderColor tcell.Color) *Message {
- return &Message{
- ID: id,
- Sender: sender,
- Text: text,
- Timestamp: timestamp,
- Date: date,
- senderColor: senderColor,
- }
-}
-
-var (
- boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
- spacePattern = regexp.MustCompile(`\s+`)
-)
-
-func (message *Message) calculateBuffer(width int) {
- if width < 1 {
- return
- }
- message.buffer = []string{}
- forcedLinebreaks := strings.Split(message.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):]
- }
- }
-}
-
type MessageView struct {
*tview.Box
@@ -106,7 +46,7 @@ type MessageView struct {
totalHeight int
messageIDs map[string]bool
- messages []*Message
+ messages []*types.Message
}
func NewMessageView() *MessageView {
@@ -119,7 +59,7 @@ func NewMessageView() *MessageView {
Separator: '|',
ScrollOffset: 0,
- messages: make([]*Message, 0),
+ messages: make([]*types.Message, 0),
messageIDs: make(map[string]bool),
widestSender: 5,
@@ -132,11 +72,11 @@ func NewMessageView() *MessageView {
}
}
-func (view *MessageView) NewMessage(id, sender, text string, timestamp time.Time) *Message {
- return NewMessage(id, sender, text,
+func (view *MessageView) NewMessage(id, sender, text string, timestamp time.Time) *types.Message {
+ return types.NewMessage(id, sender, text,
timestamp.Format(view.TimestampFormat),
timestamp.Format(view.DateFormat),
- getColor(sender))
+ GetHashColor(sender))
}
func (view *MessageView) recalculateBuffers() {
@@ -144,7 +84,7 @@ func (view *MessageView) recalculateBuffers() {
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
if width != view.prevWidth {
for _, message := range view.messages {
- message.calculateBuffer(width)
+ message.CalculateBuffer(width)
}
view.prevWidth = width
}
@@ -164,7 +104,7 @@ const (
PrependMessage
)
-func (view *MessageView) AddMessage(message *Message, direction int) {
+func (view *MessageView) AddMessage(message *types.Message, direction int) {
_, messageExists := view.messageIDs[message.ID]
if messageExists {
return
@@ -174,15 +114,15 @@ func (view *MessageView) AddMessage(message *Message, direction int) {
_, _, width, _ := view.GetInnerRect()
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
- message.calculateBuffer(width)
+ message.CalculateBuffer(width)
if direction == AppendMessage {
if view.ScrollOffset > 0 {
- view.ScrollOffset += len(message.buffer)
+ view.ScrollOffset += len(message.Buffer)
}
view.messages = append(view.messages, message)
} else if direction == PrependMessage {
- view.messages = append([]*Message{message}, view.messages...)
+ view.messages = append([]*types.Message{message}, view.messages...)
}
view.messageIDs[message.ID] = true
@@ -199,7 +139,7 @@ func (view *MessageView) recalculateHeight() {
for i := len(view.messages) - 1; i >= 0; i-- {
prevTotalHeight := view.totalHeight
message := view.messages[i]
- view.totalHeight += len(message.buffer)
+ view.totalHeight += len(message.Buffer)
if message.Date != prevDate {
if len(prevDate) != 0 {
view.totalHeight++
@@ -268,6 +208,9 @@ func (view *MessageView) writeLineRight(screen tcell.Screen, line string, x, y,
screen.SetContent(x+offsetX+localOffset, y, ch, nil, tcell.StyleDefault.Foreground(color))
}
offsetX += chWidth
+ if offsetX > maxWidth {
+ break
+ }
}
}
@@ -302,7 +245,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
prevSenderLine := -1
for i := view.firstDisplayMessage; i >= view.lastDisplayMessage; i-- {
message := view.messages[i]
- messageHeight := len(message.buffer)
+ messageHeight := len(message.Buffer)
// Show message when the date changes.
if message.Date != prevDate {
@@ -325,7 +268,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
view.writeLine(screen, message.Timestamp, x, senderAtLine, tcell.ColorDefault)
view.writeLineRight(screen, message.Sender,
x+usernameOffsetX, senderAtLine,
- view.widestSender, message.senderColor)
+ view.widestSender, message.SenderColor)
if message.Sender == prevSender {
// Sender is same as previous. We're looping from bottom to top, and we want the
@@ -333,12 +276,12 @@ func (view *MessageView) Draw(screen tcell.Screen) {
// below.
view.writeLineRight(screen, strings.Repeat(" ", view.widestSender),
x+usernameOffsetX, prevSenderLine,
- view.widestSender, message.senderColor)
+ view.widestSender, message.SenderColor)
}
prevSender = message.Sender
prevSenderLine = senderAtLine
- for num, line := range message.buffer {
+ for num, line := range message.Buffer {
offsetY := height - messageHeight - writeOffset + num
// Only render message if it's within the message view.
if offsetY >= 0 {
diff --git a/room-view.go b/ui/widget/room-view.go
index 5710788..eeab7b2 100644
--- a/room-view.go
+++ b/ui/widget/room-view.go
@@ -14,19 +14,23 @@
// 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 main
+package widget
import (
"fmt"
- "hash/fnv"
- "sort"
"strings"
"time"
"github.com/gdamore/tcell"
+ rooms "maunium.net/go/gomuks/matrix/room"
+ "maunium.net/go/gomuks/ui/types"
"maunium.net/go/tview"
)
+type Renderable interface {
+ Render()
+}
+
type RoomView struct {
*tview.Box
@@ -34,31 +38,19 @@ type RoomView struct {
content *MessageView
status *tview.TextView
userList *tview.TextView
- room *Room
-
- parent *MainView
-}
+ Room *rooms.Room
-var colorNames []string
-
-func init() {
- colorNames = make([]string, len(tcell.ColorNames))
- i := 0
- for name, _ := range tcell.ColorNames {
- colorNames[i] = name
- i++
- }
- sort.Sort(sort.StringSlice(colorNames))
+ parent Renderable
}
-func NewRoomView(parent *MainView, room *Room) *RoomView {
+func NewRoomView(parent Renderable, room *rooms.Room) *RoomView {
view := &RoomView{
Box: tview.NewBox(),
topic: tview.NewTextView(),
content: NewMessageView(),
status: tview.NewTextView(),
userList: tview.NewTextView(),
- room: room,
+ Room: room,
parent: parent,
}
view.topic.
@@ -90,9 +82,13 @@ func (view *RoomView) Draw(screen tcell.Screen) {
view.userList.Draw(screen)
}
+func (view *RoomView) SetStatus(status string) {
+ view.status.SetText(status)
+}
+
func (view *RoomView) SetTyping(users []string) {
for index, user := range users {
- member := view.room.GetMember(user)
+ member := view.Room.GetMember(user)
if member != nil {
users[index] = member.DisplayName
}
@@ -109,7 +105,7 @@ func (view *RoomView) SetTyping(users []string) {
}
func (view *RoomView) AutocompleteUser(existingText string) (completions []string) {
- for _, user := range view.room.GetMembers() {
+ for _, user := range view.Room.GetMembers() {
if strings.HasPrefix(user.DisplayName, existingText) {
completions = append(completions, user.DisplayName)
} else if strings.HasPrefix(user.UserID, existingText) {
@@ -123,38 +119,15 @@ func (view *RoomView) MessageView() *MessageView {
return view.content
}
-func getColorName(s string) string {
- switch s {
- case "-->":
- return "green"
- case "<--":
- return "red"
- case "---":
- return "yellow"
- default:
- h := fnv.New32a()
- h.Write([]byte(s))
- return colorNames[int(h.Sum32())%len(colorNames)]
- }
-}
-
-func getColor(s string) tcell.Color {
- return tcell.ColorNames[getColorName(s)]
-}
-
-func color(s string) string {
- return fmt.Sprintf("[%s]%s[white]", getColorName(s), s)
-}
-
func (view *RoomView) UpdateUserList() {
var joined strings.Builder
var invited strings.Builder
- for _, user := range view.room.GetMembers() {
+ for _, user := range view.Room.GetMembers() {
if user.Membership == "join" {
- joined.WriteString(color(user.DisplayName))
+ joined.WriteString(AddHashColor(user.DisplayName))
joined.WriteRune('\n')
} else if user.Membership == "invite" {
- invited.WriteString(color(user.DisplayName))
+ invited.WriteString(AddHashColor(user.DisplayName))
invited.WriteRune('\n')
}
}
@@ -165,15 +138,15 @@ func (view *RoomView) UpdateUserList() {
}
}
-func (view *RoomView) NewMessage(id, sender, text string, timestamp time.Time) *Message {
- member := view.room.GetMember(sender)
+func (view *RoomView) NewMessage(id, sender, text string, timestamp time.Time) *types.Message {
+ member := view.Room.GetMember(sender)
if member != nil {
sender = member.DisplayName
}
return view.content.NewMessage(id, sender, text, timestamp)
}
-func (view *RoomView) AddMessage(message *Message, direction int) {
+func (view *RoomView) AddMessage(message *types.Message, direction int) {
view.content.AddMessage(message, direction)
view.parent.Render()
}