Organize files
This commit is contained in:
		@@ -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 main
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@@ -23,6 +23,7 @@ import (
 | 
				
			|||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/yaml.v2"
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/debug"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
@@ -30,15 +31,11 @@ type Config struct {
 | 
				
			|||||||
	HS   string `yaml:"homeserver"`
 | 
						HS   string `yaml:"homeserver"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dir     string        `yaml:"-"`
 | 
						dir     string        `yaml:"-"`
 | 
				
			||||||
	gmx     Gomuks       `yaml:"-"`
 | 
					 | 
				
			||||||
	debug   DebugPrinter `yaml:"-"`
 | 
					 | 
				
			||||||
	Session *Session      `yaml:"-"`
 | 
						Session *Session      `yaml:"-"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewConfig(gmx Gomuks, dir string) *Config {
 | 
					func NewConfig(dir string) *Config {
 | 
				
			||||||
	return &Config{
 | 
						return &Config{
 | 
				
			||||||
		gmx:   gmx,
 | 
					 | 
				
			||||||
		debug: gmx.Debug(),
 | 
					 | 
				
			||||||
		dir:   dir,
 | 
							dir:   dir,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -67,14 +64,14 @@ func (config *Config) Save() {
 | 
				
			|||||||
	os.MkdirAll(config.dir, 0700)
 | 
						os.MkdirAll(config.dir, 0700)
 | 
				
			||||||
	data, err := yaml.Marshal(&config)
 | 
						data, err := yaml.Marshal(&config)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		config.debug.Print("Failed to marshal config")
 | 
							debug.Print("Failed to marshal config")
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	path := filepath.Join(config.dir, "config.yaml")
 | 
						path := filepath.Join(config.dir, "config.yaml")
 | 
				
			||||||
	err = ioutil.WriteFile(path, data, 0600)
 | 
						err = ioutil.WriteFile(path, data, 0600)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		config.debug.Print("Failed to write config to", path)
 | 
							debug.Print("Failed to write config to", path)
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -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 main
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
@@ -22,6 +22,8 @@ import (
 | 
				
			|||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"maunium.net/go/gomatrix"
 | 
						"maunium.net/go/gomatrix"
 | 
				
			||||||
 | 
						rooms "maunium.net/go/gomuks/matrix/room"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/debug"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Session struct {
 | 
					type Session struct {
 | 
				
			||||||
@@ -30,9 +32,7 @@ type Session struct {
 | 
				
			|||||||
	AccessToken string
 | 
						AccessToken string
 | 
				
			||||||
	NextBatch   string
 | 
						NextBatch   string
 | 
				
			||||||
	FilterID    string
 | 
						FilterID    string
 | 
				
			||||||
	Rooms       map[string]*Room
 | 
						Rooms       map[string]*rooms.Room
 | 
				
			||||||
 | 
					 | 
				
			||||||
	debug DebugPrinter `json:"-"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (config *Config) LoadSession(mxid string) {
 | 
					func (config *Config) LoadSession(mxid string) {
 | 
				
			||||||
@@ -44,13 +44,12 @@ func (config *Config) NewSession(mxid string) *Session {
 | 
				
			|||||||
	return &Session{
 | 
						return &Session{
 | 
				
			||||||
		MXID:  mxid,
 | 
							MXID:  mxid,
 | 
				
			||||||
		path:  filepath.Join(config.dir, mxid+".session"),
 | 
							path:  filepath.Join(config.dir, mxid+".session"),
 | 
				
			||||||
		Rooms: make(map[string]*Room),
 | 
							Rooms: make(map[string]*rooms.Room),
 | 
				
			||||||
		debug: config.debug,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Session) Clear() {
 | 
					func (s *Session) Clear() {
 | 
				
			||||||
	s.Rooms = make(map[string]*Room)
 | 
						s.Rooms = make(map[string]*rooms.Room)
 | 
				
			||||||
	s.NextBatch = ""
 | 
						s.NextBatch = ""
 | 
				
			||||||
	s.FilterID = ""
 | 
						s.FilterID = ""
 | 
				
			||||||
	s.Save()
 | 
						s.Save()
 | 
				
			||||||
@@ -59,13 +58,13 @@ func (s *Session) Clear() {
 | 
				
			|||||||
func (s *Session) Load() {
 | 
					func (s *Session) Load() {
 | 
				
			||||||
	data, err := ioutil.ReadFile(s.path)
 | 
						data, err := ioutil.ReadFile(s.path)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		s.debug.Print("Failed to read session from", s.path)
 | 
							debug.Print("Failed to read session from", s.path)
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = json.Unmarshal(data, s)
 | 
						err = json.Unmarshal(data, s)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		s.debug.Print("Failed to parse session at", s.path)
 | 
							debug.Print("Failed to parse session at", s.path)
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -73,13 +72,13 @@ func (s *Session) Load() {
 | 
				
			|||||||
func (s *Session) Save() {
 | 
					func (s *Session) Save() {
 | 
				
			||||||
	data, err := json.Marshal(s)
 | 
						data, err := json.Marshal(s)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		s.debug.Print("Failed to marshal session of", s.MXID)
 | 
							debug.Print("Failed to marshal session of", s.MXID)
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = ioutil.WriteFile(s.path, data, 0600)
 | 
						err = ioutil.WriteFile(s.path, data, 0600)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		s.debug.Print("Failed to write session to", s.path)
 | 
							debug.Print("Failed to write session to", s.path)
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -92,16 +91,16 @@ func (s *Session) LoadNextBatch(_ string) string {
 | 
				
			|||||||
	return s.NextBatch
 | 
						return s.NextBatch
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Session) GetRoom(mxid string) *Room {
 | 
					func (s *Session) GetRoom(mxid string) *rooms.Room {
 | 
				
			||||||
	room, _ := s.Rooms[mxid]
 | 
						room, _ := s.Rooms[mxid]
 | 
				
			||||||
	if room == nil {
 | 
						if room == nil {
 | 
				
			||||||
		room = NewRoom(mxid)
 | 
							room = rooms.NewRoom(mxid)
 | 
				
			||||||
		s.Rooms[room.ID] = room
 | 
							s.Rooms[room.ID] = room
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return room
 | 
						return room
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Session) PutRoom(room *Room) {
 | 
					func (s *Session) PutRoom(room *rooms.Room) {
 | 
				
			||||||
	s.Rooms[room.ID] = room
 | 
						s.Rooms[room.ID] = room
 | 
				
			||||||
	s.Save()
 | 
						s.Save()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								go.mod
									
									
									
									
									
								
							@@ -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
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
							
								
								
									
										60
									
								
								gomuks.go
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								gomuks.go
									
									
									
									
									
								
							@@ -21,43 +21,37 @@ import (
 | 
				
			|||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"maunium.net/go/gomatrix"
 | 
						"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"
 | 
						"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 {
 | 
					type gomuks struct {
 | 
				
			||||||
	app    *tview.Application
 | 
						app    *tview.Application
 | 
				
			||||||
	ui     *GomuksUI
 | 
						ui     *ui.GomuksUI
 | 
				
			||||||
	matrix *MatrixContainer
 | 
						matrix *matrix.Container
 | 
				
			||||||
	debug  *DebugPane
 | 
						debug  *debug.Pane
 | 
				
			||||||
	config *Config
 | 
						config *config.Config
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var gdebug DebugPrinter
 | 
					func NewGomuks(enableDebug bool) *gomuks {
 | 
				
			||||||
 | 
					 | 
				
			||||||
func NewGomuks(debug 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(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	gmx.debug = NewDebugPane(gmx)
 | 
					
 | 
				
			||||||
	gdebug = gmx.debug
 | 
						gmx.debug = debug.NewPane()
 | 
				
			||||||
	gmx.config = NewConfig(gmx, configDir)
 | 
						gmx.debug.SetChangedFunc(func() {
 | 
				
			||||||
	gmx.ui = NewGomuksUI(gmx)
 | 
							gmx.ui.Render()
 | 
				
			||||||
	gmx.matrix = NewMatrixContainer(gmx)
 | 
						})
 | 
				
			||||||
	gmx.ui.matrix = gmx.matrix
 | 
						debug.Default = gmx.debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gmx.config = config.NewConfig(configDir)
 | 
				
			||||||
 | 
						gmx.ui = ui.NewGomuksUI(gmx)
 | 
				
			||||||
 | 
						gmx.matrix = matrix.NewMatrixContainer(gmx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gmx.config.Load()
 | 
						gmx.config.Load()
 | 
				
			||||||
	if len(gmx.config.MXID) > 0 {
 | 
						if len(gmx.config.MXID) > 0 {
 | 
				
			||||||
@@ -67,7 +61,7 @@ func NewGomuks(debug bool) *gomuks {
 | 
				
			|||||||
	gmx.matrix.InitClient()
 | 
						gmx.matrix.InitClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	main := gmx.ui.InitViews()
 | 
						main := gmx.ui.InitViews()
 | 
				
			||||||
	if debug {
 | 
						if enableDebug {
 | 
				
			||||||
		main = gmx.debug.Wrap(main)
 | 
							main = gmx.debug.Wrap(main)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	gmx.app.SetRoot(main, true)
 | 
						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 {
 | 
					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
 | 
						return gmx.matrix
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -117,11 +107,11 @@ func (gmx *gomuks) App() *tview.Application {
 | 
				
			|||||||
	return gmx.app
 | 
						return gmx.app
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (gmx *gomuks) Config() *Config {
 | 
					func (gmx *gomuks) Config() *config.Config {
 | 
				
			||||||
	return gmx.config
 | 
						return gmx.config
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (gmx *gomuks) UI() *GomuksUI {
 | 
					func (gmx *gomuks) UI() ifc.GomuksUI {
 | 
				
			||||||
	return gmx.ui
 | 
						return gmx.ui
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								interface/gomuks.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								interface/gomuks.go
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								interface/matrix.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								interface/matrix.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								interface/ui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								interface/ui.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 main
 | 
					package matrix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@@ -22,24 +22,27 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"maunium.net/go/gomatrix"
 | 
						"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
 | 
						client  *gomatrix.Client
 | 
				
			||||||
	gmx     Gomuks
 | 
						gmx     ifc.Gomuks
 | 
				
			||||||
	ui      *GomuksUI
 | 
						ui      ifc.GomuksUI
 | 
				
			||||||
	debug   DebugPrinter
 | 
						config  *config.Config
 | 
				
			||||||
	config  *Config
 | 
					 | 
				
			||||||
	running bool
 | 
						running bool
 | 
				
			||||||
	stop    chan bool
 | 
						stop    chan bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	typing int64
 | 
						typing int64
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewMatrixContainer(gmx Gomuks) *MatrixContainer {
 | 
					func NewMatrixContainer(gmx ifc.Gomuks) *Container {
 | 
				
			||||||
	c := &MatrixContainer{
 | 
						c := &Container{
 | 
				
			||||||
		config: gmx.Config(),
 | 
							config: gmx.Config(),
 | 
				
			||||||
		debug:  gmx.Debug(),
 | 
					 | 
				
			||||||
		ui:     gmx.UI(),
 | 
							ui:     gmx.UI(),
 | 
				
			||||||
		gmx:    gmx,
 | 
							gmx:    gmx,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -47,7 +50,7 @@ func NewMatrixContainer(gmx Gomuks) *MatrixContainer {
 | 
				
			|||||||
	return c
 | 
						return c
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) InitClient() error {
 | 
					func (c *Container) InitClient() error {
 | 
				
			||||||
	if len(c.config.HS) == 0 {
 | 
						if len(c.config.HS) == 0 {
 | 
				
			||||||
		return fmt.Errorf("no homeserver in config")
 | 
							return fmt.Errorf("no homeserver in config")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -77,11 +80,11 @@ func (c *MatrixContainer) InitClient() error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) Initialized() bool {
 | 
					func (c *Container) Initialized() bool {
 | 
				
			||||||
	return c.client != nil
 | 
						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{
 | 
						resp, err := c.client.Login(&gomatrix.ReqLogin{
 | 
				
			||||||
		Type:     "m.login.password",
 | 
							Type:     "m.login.password",
 | 
				
			||||||
		User:     user,
 | 
							User:     user,
 | 
				
			||||||
@@ -103,24 +106,28 @@ func (c *MatrixContainer) Login(user, password string) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) Stop() {
 | 
					func (c *Container) Stop() {
 | 
				
			||||||
	if c.running {
 | 
						if c.running {
 | 
				
			||||||
		c.stop <- true
 | 
							c.stop <- true
 | 
				
			||||||
		c.client.StopSync()
 | 
							c.client.StopSync()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) UpdateRoomList() {
 | 
					func (c *Container) Client() *gomatrix.Client {
 | 
				
			||||||
	rooms, err := c.client.JoinedRooms()
 | 
						return c.client
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Container) UpdateRoomList() {
 | 
				
			||||||
 | 
						resp, err := c.client.JoinedRooms()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.debug.Print("Error fetching room list:", err)
 | 
							debug.Print("Error fetching room list:", err)
 | 
				
			||||||
		return
 | 
							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
 | 
						c.client.Store = c.config.Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	syncer := NewGomuksSyncer(c.config.Session)
 | 
						syncer := NewGomuksSyncer(c.config.Session)
 | 
				
			||||||
@@ -132,38 +139,38 @@ func (c *MatrixContainer) OnLogin() {
 | 
				
			|||||||
	c.UpdateRoomList()
 | 
						c.UpdateRoomList()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) Start() {
 | 
					func (c *Container) Start() {
 | 
				
			||||||
	defer c.gmx.Recover()
 | 
						defer c.gmx.Recover()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.ui.SetView(ifc.ViewMain)
 | 
				
			||||||
	c.OnLogin()
 | 
						c.OnLogin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.debug.Print("Starting sync...")
 | 
						debug.Print("Starting sync...")
 | 
				
			||||||
	c.running = true
 | 
						c.running = true
 | 
				
			||||||
	c.ui.SetView(ViewMain)
 | 
					 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-c.stop:
 | 
							case <-c.stop:
 | 
				
			||||||
			c.debug.Print("Stopping sync...")
 | 
								debug.Print("Stopping sync...")
 | 
				
			||||||
			c.running = false
 | 
								c.running = false
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			if err := c.client.Sync(); err != nil {
 | 
								if err := c.client.Sync(); err != nil {
 | 
				
			||||||
				c.debug.Print("Sync() errored", err)
 | 
									debug.Print("Sync() errored", err)
 | 
				
			||||||
			} else {
 | 
								} 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)
 | 
						room, message := c.ui.MainView().ProcessMessageEvent(evt)
 | 
				
			||||||
	if room != nil {
 | 
						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
 | 
						const Hour = 1 * 60 * 60 * 1000
 | 
				
			||||||
	if evt.Unsigned.Age > Hour {
 | 
						if evt.Unsigned.Age > Hour {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -172,15 +179,15 @@ func (c *MatrixContainer) HandleMembership(evt *gomatrix.Event) {
 | 
				
			|||||||
	room, message := c.ui.MainView().ProcessMembershipEvent(evt, true)
 | 
						room, message := c.ui.MainView().ProcessMembershipEvent(evt, true)
 | 
				
			||||||
	if room != nil {
 | 
						if room != nil {
 | 
				
			||||||
		// TODO this shouldn't be necessary
 | 
							// TODO this shouldn't be necessary
 | 
				
			||||||
		room.room.UpdateState(evt)
 | 
							room.Room.UpdateState(evt)
 | 
				
			||||||
		// TODO This should probably also be in a different place
 | 
							// TODO This should probably also be in a different place
 | 
				
			||||||
		room.UpdateUserList()
 | 
							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{})
 | 
						users := evt.Content["user_ids"].([]interface{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	strUsers := make([]string, len(users))
 | 
						strUsers := make([]string, len(users))
 | 
				
			||||||
@@ -190,29 +197,29 @@ func (c *MatrixContainer) HandleTyping(evt *gomatrix.Event) {
 | 
				
			|||||||
	c.ui.MainView().SetTyping(evt.RoomID, strUsers)
 | 
						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.gmx.Recover()
 | 
				
			||||||
	c.SendTyping(roomID, false)
 | 
						c.SendTyping(roomID, false)
 | 
				
			||||||
	c.client.SendText(roomID, message)
 | 
						c.client.SendText(roomID, message)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) SendTyping(roomID string, typing bool) {
 | 
					func (c *Container) SendTyping(roomID string, typing bool) {
 | 
				
			||||||
	c.gmx.Recover()
 | 
						c.gmx.Recover()
 | 
				
			||||||
	time := time.Now().Unix()
 | 
						ts := time.Now().Unix()
 | 
				
			||||||
	if c.typing > time && typing {
 | 
						if c.typing > ts && typing {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if typing {
 | 
						if typing {
 | 
				
			||||||
		c.client.UserTyping(roomID, true, 5000)
 | 
							c.client.UserTyping(roomID, true, 5000)
 | 
				
			||||||
		c.typing = time + 5
 | 
							c.typing = ts + 5
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		c.client.UserTyping(roomID, false, 0)
 | 
							c.client.UserTyping(roomID, false, 0)
 | 
				
			||||||
		c.typing = 0
 | 
							c.typing = 0
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) JoinRoom(roomID string) error {
 | 
					func (c *Container) JoinRoom(roomID string) error {
 | 
				
			||||||
	if len(roomID) == 0 {
 | 
						if len(roomID) == 0 {
 | 
				
			||||||
		return fmt.Errorf("invalid room ID")
 | 
							return fmt.Errorf("invalid room ID")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -222,26 +229,40 @@ func (c *MatrixContainer) JoinRoom(roomID string) error {
 | 
				
			|||||||
		server = roomID[strings.Index(roomID, ":")+1:]
 | 
							server = roomID[strings.Index(roomID, ":")+1:]
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	resp, err := c.client.JoinRoom(roomID, server, nil)
 | 
						_, err := c.client.JoinRoom(roomID, server, nil)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.ui.MainView().AddRoom(resp.RoomID)
 | 
						// TODO probably safe to remove
 | 
				
			||||||
 | 
						// c.ui.MainView().AddRoom(resp.RoomID)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *MatrixContainer) getState(roomID string) []*gomatrix.Event {
 | 
					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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Container) getState(roomID string) []*gomatrix.Event {
 | 
				
			||||||
	content := make([]*gomatrix.Event, 0)
 | 
						content := make([]*gomatrix.Event, 0)
 | 
				
			||||||
	err := c.client.StateEvent(roomID, "", "", &content)
 | 
						err := c.client.StateEvent(roomID, "", "", &content)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.debug.Print("Error getting state of", roomID, err)
 | 
							debug.Print("Error getting state of", roomID, err)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return content
 | 
						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)
 | 
						resp, err := c.client.Messages(roomID, prevBatch, "", 'b', limit)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, "", err
 | 
							return nil, "", err
 | 
				
			||||||
@@ -249,7 +270,7 @@ func (c *MatrixContainer) GetHistory(roomID, prevBatch string, limit int) ([]gom
 | 
				
			|||||||
	return resp.Chunk, resp.End, nil
 | 
						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)
 | 
						room := c.config.Session.GetRoom(roomID)
 | 
				
			||||||
	if room != nil && len(room.State) == 0 {
 | 
						if room != nil && len(room.State) == 0 {
 | 
				
			||||||
		events := c.getState(room.ID)
 | 
							events := c.getState(room.ID)
 | 
				
			||||||
							
								
								
									
										51
									
								
								matrix/room/member.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								matrix/room/member.go
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 main
 | 
					package room
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"maunium.net/go/gomatrix"
 | 
						"maunium.net/go/gomatrix"
 | 
				
			||||||
@@ -25,7 +25,7 @@ type Room struct {
 | 
				
			|||||||
	*gomatrix.Room
 | 
						*gomatrix.Room
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	PrevBatch   string
 | 
						PrevBatch   string
 | 
				
			||||||
	memberCache map[string]*RoomMember
 | 
						memberCache map[string]*Member
 | 
				
			||||||
	nameCache   string
 | 
						nameCache   string
 | 
				
			||||||
	topicCache  string
 | 
						topicCache  string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -107,38 +107,8 @@ func (room *Room) GetTitle() string {
 | 
				
			|||||||
	return room.nameCache
 | 
						return room.nameCache
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type RoomMember struct {
 | 
					func (room *Room) createMemberCache() map[string]*Member {
 | 
				
			||||||
	UserID      string `json:"-"`
 | 
						cache := make(map[string]*Member)
 | 
				
			||||||
	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)
 | 
					 | 
				
			||||||
	events := room.GetStateEvents("m.room.member")
 | 
						events := room.GetStateEvents("m.room.member")
 | 
				
			||||||
	if events != nil {
 | 
						if events != nil {
 | 
				
			||||||
		for userID, event := range events {
 | 
							for userID, event := range events {
 | 
				
			||||||
@@ -152,14 +122,14 @@ func (room *Room) createMemberCache() map[string]*RoomMember {
 | 
				
			|||||||
	return cache
 | 
						return cache
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (room *Room) GetMembers() map[string]*RoomMember {
 | 
					func (room *Room) GetMembers() map[string]*Member {
 | 
				
			||||||
	if len(room.memberCache) == 0 {
 | 
						if len(room.memberCache) == 0 {
 | 
				
			||||||
		room.createMemberCache()
 | 
							room.createMemberCache()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return room.memberCache
 | 
						return room.memberCache
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (room *Room) GetMember(userID string) *RoomMember {
 | 
					func (room *Room) GetMember(userID string) *Member {
 | 
				
			||||||
	if len(room.memberCache) == 0 {
 | 
						if len(room.memberCache) == 0 {
 | 
				
			||||||
		room.createMemberCache()
 | 
							room.createMemberCache()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
package main
 | 
					package matrix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
@@ -7,18 +7,19 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"maunium.net/go/gomatrix"
 | 
						"maunium.net/go/gomatrix"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/config"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GomuksSyncer is the default syncing implementation. You can either write your own syncer, or selectively
 | 
					// 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
 | 
					// 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.
 | 
					// pattern to notify callers about incoming events. See GomuksSyncer.OnEventType for more information.
 | 
				
			||||||
type GomuksSyncer struct {
 | 
					type GomuksSyncer struct {
 | 
				
			||||||
	Session *Session
 | 
						Session   *config.Session
 | 
				
			||||||
	listeners map[string][]gomatrix.OnEventListener // event type to listeners array
 | 
						listeners map[string][]gomatrix.OnEventListener // event type to listeners array
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewGomuksSyncer returns an instantiated GomuksSyncer
 | 
					// NewGomuksSyncer returns an instantiated GomuksSyncer
 | 
				
			||||||
func NewGomuksSyncer(session *Session) *GomuksSyncer {
 | 
					func NewGomuksSyncer(session *config.Session) *GomuksSyncer {
 | 
				
			||||||
	return &GomuksSyncer{
 | 
						return &GomuksSyncer{
 | 
				
			||||||
		Session:   session,
 | 
							Session:   session,
 | 
				
			||||||
		listeners: make(map[string][]gomatrix.OnEventListener),
 | 
							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 {
 | 
						for _, event := range res.Presence.Events {
 | 
				
			||||||
		s.notifyListeners(&event)
 | 
							s.notifyListeners(event)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for roomID, roomData := range res.Rooms.Join {
 | 
						for roomID, roomData := range res.Rooms.Join {
 | 
				
			||||||
		room := s.Session.GetRoom(roomID)
 | 
							room := s.Session.GetRoom(roomID)
 | 
				
			||||||
		for _, event := range roomData.State.Events {
 | 
							for _, event := range roomData.State.Events {
 | 
				
			||||||
			event.RoomID = roomID
 | 
								event.RoomID = roomID
 | 
				
			||||||
			room.UpdateState(&event)
 | 
								room.UpdateState(event)
 | 
				
			||||||
			s.notifyListeners(&event)
 | 
								s.notifyListeners(event)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for _, event := range roomData.Timeline.Events {
 | 
							for _, event := range roomData.Timeline.Events {
 | 
				
			||||||
			event.RoomID = roomID
 | 
								event.RoomID = roomID
 | 
				
			||||||
			s.notifyListeners(&event)
 | 
								s.notifyListeners(event)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for _, event := range roomData.Ephemeral.Events {
 | 
							for _, event := range roomData.Ephemeral.Events {
 | 
				
			||||||
			event.RoomID = roomID
 | 
								event.RoomID = roomID
 | 
				
			||||||
			s.notifyListeners(&event)
 | 
								s.notifyListeners(event)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if len(room.PrevBatch) == 0 {
 | 
							if len(room.PrevBatch) == 0 {
 | 
				
			||||||
@@ -64,8 +65,8 @@ func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (er
 | 
				
			|||||||
		room := s.Session.GetRoom(roomID)
 | 
							room := s.Session.GetRoom(roomID)
 | 
				
			||||||
		for _, event := range roomData.State.Events {
 | 
							for _, event := range roomData.State.Events {
 | 
				
			||||||
			event.RoomID = roomID
 | 
								event.RoomID = roomID
 | 
				
			||||||
			room.UpdateState(&event)
 | 
								room.UpdateState(event)
 | 
				
			||||||
			s.notifyListeners(&event)
 | 
								s.notifyListeners(event)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for roomID, roomData := range res.Rooms.Leave {
 | 
						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 {
 | 
							for _, event := range roomData.Timeline.Events {
 | 
				
			||||||
			if event.StateKey != nil {
 | 
								if event.StateKey != nil {
 | 
				
			||||||
				event.RoomID = roomID
 | 
									event.RoomID = roomID
 | 
				
			||||||
				room.UpdateState(&event)
 | 
									room.UpdateState(event)
 | 
				
			||||||
				s.notifyListeners(&event)
 | 
									s.notifyListeners(event)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -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 main
 | 
					package debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@@ -22,54 +22,62 @@ import (
 | 
				
			|||||||
	"maunium.net/go/tview"
 | 
						"maunium.net/go/tview"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DebugPaneHeight = 35
 | 
					type Printer interface {
 | 
				
			||||||
 | 
					 | 
				
			||||||
type DebugPrinter interface {
 | 
					 | 
				
			||||||
	Printf(text string, args ...interface{})
 | 
						Printf(text string, args ...interface{})
 | 
				
			||||||
	Print(text ...interface{})
 | 
						Print(text ...interface{})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DebugPane struct {
 | 
					type Pane struct {
 | 
				
			||||||
	pane *tview.TextView
 | 
						*tview.TextView
 | 
				
			||||||
 | 
						Height int
 | 
				
			||||||
	num  int
 | 
						num  int
 | 
				
			||||||
	gmx  Gomuks
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewDebugPane(gmx Gomuks) *DebugPane {
 | 
					var Default Printer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewPane() *Pane {
 | 
				
			||||||
	pane := tview.NewTextView()
 | 
						pane := tview.NewTextView()
 | 
				
			||||||
	pane.
 | 
						pane.
 | 
				
			||||||
		SetScrollable(true).
 | 
							SetScrollable(true).
 | 
				
			||||||
		SetWrap(true)
 | 
							SetWrap(true).
 | 
				
			||||||
	pane.SetChangedFunc(func() {
 | 
							SetBorder(true).
 | 
				
			||||||
		gmx.App().Draw()
 | 
							SetTitle("Debug output")
 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	pane.SetBorder(true).SetTitle("Debug output")
 | 
					 | 
				
			||||||
	fmt.Fprintln(pane, "[0] Debug pane initialized")
 | 
						fmt.Fprintln(pane, "[0] Debug pane initialized")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &DebugPane{
 | 
						return &Pane{
 | 
				
			||||||
		pane: pane,
 | 
							TextView: pane,
 | 
				
			||||||
 | 
							Height: 35,
 | 
				
			||||||
		num:  0,
 | 
							num:  0,
 | 
				
			||||||
		gmx:  gmx,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DebugPane) Printf(text string, args ...interface{}) {
 | 
					func (db *Pane) Printf(text string, args ...interface{}) {
 | 
				
			||||||
	db.Write(fmt.Sprintf(text, args...) + "\n")
 | 
						db.WriteString(fmt.Sprintf(text, args...) + "\n")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DebugPane) Print(text ...interface{}) {
 | 
					func (db *Pane) Print(text ...interface{}) {
 | 
				
			||||||
	db.Write(fmt.Sprintln(text...))
 | 
						db.WriteString(fmt.Sprintln(text...))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DebugPane) Write(text string) {
 | 
					func (db *Pane) WriteString(text string) {
 | 
				
			||||||
	if db.pane != nil {
 | 
					 | 
				
			||||||
	db.num++
 | 
						db.num++
 | 
				
			||||||
		fmt.Fprintf(db.pane, "[%d] %s", db.num, text)
 | 
						fmt.Fprintf(db, "[%d] %s", db.num, text)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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, 1, 0, 1, 1, 1, 1, false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Printf(text string, args ...interface{}) {
 | 
				
			||||||
 | 
						if Default != nil {
 | 
				
			||||||
 | 
							Default.Printf(text, args...)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (db *DebugPane) Wrap(main tview.Primitive) tview.Primitive {
 | 
					func Print(text ...interface{}) {
 | 
				
			||||||
	return tview.NewGrid().SetRows(0, DebugPaneHeight).SetColumns(0).
 | 
						if Default != nil {
 | 
				
			||||||
		AddItem(main, 0, 0, 1, 1, 1, 1, true).
 | 
							Default.Print(text...)
 | 
				
			||||||
		AddItem(db.pane, 1, 0, 1, 1, 1, 1, false)
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										85
									
								
								ui/types/message.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								ui/types/message.go
									
									
									
									
									
										Normal file
									
								
							@@ -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):]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -14,25 +14,17 @@
 | 
				
			|||||||
// 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 main
 | 
					package ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"github.com/gdamore/tcell"
 | 
						"github.com/gdamore/tcell"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/interface"
 | 
				
			||||||
	"maunium.net/go/tview"
 | 
						"maunium.net/go/tview"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Allowed views in GomuksUI
 | 
					 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	ViewLogin = "login"
 | 
					 | 
				
			||||||
	ViewMain  = "main"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type GomuksUI struct {
 | 
					type GomuksUI struct {
 | 
				
			||||||
	gmx    Gomuks
 | 
						gmx    ifc.Gomuks
 | 
				
			||||||
	app    *tview.Application
 | 
						app    *tview.Application
 | 
				
			||||||
	matrix *MatrixContainer
 | 
					 | 
				
			||||||
	debug  DebugPrinter
 | 
					 | 
				
			||||||
	config *Config
 | 
					 | 
				
			||||||
	views  *tview.Pages
 | 
						views  *tview.Pages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mainView  *MainView
 | 
						mainView  *MainView
 | 
				
			||||||
@@ -44,13 +36,10 @@ func init() {
 | 
				
			|||||||
	tview.Styles.ContrastBackgroundColor = tcell.ColorDarkGreen
 | 
						tview.Styles.ContrastBackgroundColor = tcell.ColorDarkGreen
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewGomuksUI(gmx Gomuks) (ui *GomuksUI) {
 | 
					func NewGomuksUI(gmx ifc.Gomuks) (ui *GomuksUI) {
 | 
				
			||||||
	ui = &GomuksUI{
 | 
						ui = &GomuksUI{
 | 
				
			||||||
		gmx:    gmx,
 | 
							gmx:    gmx,
 | 
				
			||||||
		app:    gmx.App(),
 | 
							app:    gmx.App(),
 | 
				
			||||||
		matrix: gmx.MatrixContainer(),
 | 
					 | 
				
			||||||
		debug:  gmx.Debug(),
 | 
					 | 
				
			||||||
		config: gmx.Config(),
 | 
					 | 
				
			||||||
		views:  tview.NewPages(),
 | 
							views:  tview.NewPages(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ui.views.SetChangedFunc(ui.Render)
 | 
						ui.views.SetChangedFunc(ui.Render)
 | 
				
			||||||
@@ -61,16 +50,16 @@ func (ui *GomuksUI) Render() {
 | 
				
			|||||||
	ui.app.Draw()
 | 
						ui.app.Draw()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ui *GomuksUI) SetView(name string) {
 | 
					func (ui *GomuksUI) SetView(name ifc.View) {
 | 
				
			||||||
	ui.views.SwitchToPage(name)
 | 
						ui.views.SwitchToPage(string(name))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ui *GomuksUI) InitViews() tview.Primitive {
 | 
					func (ui *GomuksUI) InitViews() tview.Primitive {
 | 
				
			||||||
	ui.views.AddPage(ViewLogin, ui.NewLoginView(), true, true)
 | 
						ui.views.AddPage(string(ifc.ViewLogin), ui.NewLoginView(), true, true)
 | 
				
			||||||
	ui.views.AddPage(ViewMain, ui.NewMainView(), true, false)
 | 
						ui.views.AddPage(string(ifc.ViewMain), ui.NewMainView(), true, false)
 | 
				
			||||||
	return ui.views
 | 
						return ui.views
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ui *GomuksUI) MainView() *MainView {
 | 
					func (ui *GomuksUI) MainView() ifc.MainView {
 | 
				
			||||||
	return ui.mainView
 | 
						return ui.mainView
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -14,14 +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 main
 | 
					package ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/debug"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/widget"
 | 
				
			||||||
	"maunium.net/go/tview"
 | 
						"maunium.net/go/tview"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ui *GomuksUI) NewLoginView() tview.Primitive {
 | 
					func (ui *GomuksUI) NewLoginView() tview.Primitive {
 | 
				
			||||||
	hs := ui.config.HS
 | 
						hs := ui.gmx.Config().HS
 | 
				
			||||||
	if len(hs) == 0 {
 | 
						if len(hs) == 0 {
 | 
				
			||||||
		hs = "https://matrix.org"
 | 
							hs = "https://matrix.org"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -29,13 +31,13 @@ func (ui *GomuksUI) NewLoginView() tview.Primitive {
 | 
				
			|||||||
	ui.loginView = tview.NewForm()
 | 
						ui.loginView = tview.NewForm()
 | 
				
			||||||
	ui.loginView.
 | 
						ui.loginView.
 | 
				
			||||||
		AddInputField("Homeserver", hs, 30, nil, nil).
 | 
							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).
 | 
							AddPasswordField("Password", "", 30, '*', nil).
 | 
				
			||||||
		AddButton("Log in", ui.login).
 | 
							AddButton("Log in", ui.login).
 | 
				
			||||||
		AddButton("Quit", ui.gmx.Stop).
 | 
							AddButton("Quit", ui.gmx.Stop).
 | 
				
			||||||
		SetButtonsAlign(tview.AlignCenter).
 | 
							SetButtonsAlign(tview.AlignCenter).
 | 
				
			||||||
		SetBorder(true).SetTitle("Log in to Matrix")
 | 
							SetBorder(true).SetTitle("Log in to Matrix")
 | 
				
			||||||
	return Center(45, 11, ui.loginView)
 | 
						return widget.Center(45, 11, ui.loginView)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ui *GomuksUI) login() {
 | 
					func (ui *GomuksUI) login() {
 | 
				
			||||||
@@ -43,8 +45,9 @@ func (ui *GomuksUI) login() {
 | 
				
			|||||||
	mxid := ui.loginView.GetFormItem(1).(*tview.InputField).GetText()
 | 
						mxid := ui.loginView.GetFormItem(1).(*tview.InputField).GetText()
 | 
				
			||||||
	password := ui.loginView.GetFormItem(2).(*tview.InputField).GetText()
 | 
						password := ui.loginView.GetFormItem(2).(*tview.InputField).GetText()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ui.debug.Printf("Logging into %s as %s...", hs, mxid)
 | 
						debug.Printf("Logging into %s as %s...", hs, mxid)
 | 
				
			||||||
	ui.config.HS = hs
 | 
						ui.gmx.Config().HS = hs
 | 
				
			||||||
	ui.debug.Print("Connect result:", ui.matrix.InitClient())
 | 
						mx := ui.gmx.MatrixContainer()
 | 
				
			||||||
	ui.debug.Print("Login result:", ui.matrix.Login(mxid, password))
 | 
						debug.Print("Connect result:", mx.InitClient())
 | 
				
			||||||
 | 
						debug.Print("Login result:", mx.Login(mxid, password))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -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 main
 | 
					package ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@@ -26,6 +26,11 @@ import (
 | 
				
			|||||||
	"github.com/gdamore/tcell"
 | 
						"github.com/gdamore/tcell"
 | 
				
			||||||
	"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/interface"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/debug"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/types"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/widget"
 | 
				
			||||||
	"maunium.net/go/tview"
 | 
						"maunium.net/go/tview"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,15 +39,14 @@ type MainView struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	roomList         *tview.List
 | 
						roomList         *tview.List
 | 
				
			||||||
	roomView         *tview.Pages
 | 
						roomView         *tview.Pages
 | 
				
			||||||
	rooms            map[string]*RoomView
 | 
						rooms            map[string]*widget.RoomView
 | 
				
			||||||
	input            *AdvancedInputField
 | 
						input            *widget.AdvancedInputField
 | 
				
			||||||
	currentRoomIndex int
 | 
						currentRoomIndex int
 | 
				
			||||||
	roomIDs          []string
 | 
						roomIDs          []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	matrix *MatrixContainer
 | 
						matrix ifc.MatrixContainer
 | 
				
			||||||
	debug  DebugPrinter
 | 
						gmx    ifc.Gomuks
 | 
				
			||||||
	gmx    Gomuks
 | 
						config *config.Config
 | 
				
			||||||
	config *Config
 | 
					 | 
				
			||||||
	parent *GomuksUI
 | 
						parent *GomuksUI
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,13 +59,12 @@ func (ui *GomuksUI) NewMainView() tview.Primitive {
 | 
				
			|||||||
		Grid:     tview.NewGrid(),
 | 
							Grid:     tview.NewGrid(),
 | 
				
			||||||
		roomList: tview.NewList(),
 | 
							roomList: tview.NewList(),
 | 
				
			||||||
		roomView: tview.NewPages(),
 | 
							roomView: tview.NewPages(),
 | 
				
			||||||
		rooms:    make(map[string]*RoomView),
 | 
							rooms:    make(map[string]*widget.RoomView),
 | 
				
			||||||
		input:    NewAdvancedInputField(),
 | 
							input:    widget.NewAdvancedInputField(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		matrix: ui.matrix,
 | 
							matrix: ui.gmx.MatrixContainer(),
 | 
				
			||||||
		debug:  ui.debug,
 | 
					 | 
				
			||||||
		gmx:    ui.gmx,
 | 
							gmx:    ui.gmx,
 | 
				
			||||||
		config: ui.config,
 | 
							config: ui.gmx.Config(),
 | 
				
			||||||
		parent: ui,
 | 
							parent: ui,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,7 +86,7 @@ func (ui *GomuksUI) NewMainView() tview.Primitive {
 | 
				
			|||||||
		SetInputCapture(mainView.InputCapture)
 | 
							SetInputCapture(mainView.InputCapture)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mainView.addItem(mainView.roomList, 0, 0, 2, 1)
 | 
						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.roomView, 0, 2, 1, 1)
 | 
				
			||||||
	mainView.AddItem(mainView.input, 1, 2, 1, 1, 0, 0, true)
 | 
						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 {
 | 
							if len(userCompletions) == 1 {
 | 
				
			||||||
			text = str[0:len(str)-len(word)] + userCompletions[0] + text[len(str):]
 | 
								text = str[0:len(str)-len(word)] + userCompletions[0] + text[len(str):]
 | 
				
			||||||
		} else if len(userCompletions) > 1 && len(userCompletions) < 6 {
 | 
							} else if len(userCompletions) > 1 && len(userCompletions) < 6 {
 | 
				
			||||||
			roomView.status.Clear()
 | 
								roomView.SetStatus(fmt.Sprintf("Completions: %s", strings.Join(userCompletions, ", ")))
 | 
				
			||||||
			fmt.Fprintf(roomView.status, "Completions: %s", strings.Join(userCompletions, ", "))
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return text
 | 
						return text
 | 
				
			||||||
@@ -147,7 +149,7 @@ func (view *MainView) InputDone(key tcell.Key) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (view *MainView) HandleCommand(room, command string, args []string) {
 | 
					func (view *MainView) HandleCommand(room, command string, args []string) {
 | 
				
			||||||
	view.gmx.Recover()
 | 
						view.gmx.Recover()
 | 
				
			||||||
	view.debug.Print("Handling command", command, args)
 | 
						debug.Print("Handling command", command, args)
 | 
				
			||||||
	switch command {
 | 
						switch command {
 | 
				
			||||||
	case "/quit":
 | 
						case "/quit":
 | 
				
			||||||
		view.gmx.Stop()
 | 
							view.gmx.Stop()
 | 
				
			||||||
@@ -157,13 +159,13 @@ func (view *MainView) HandleCommand(room, command string, args []string) {
 | 
				
			|||||||
	case "/part":
 | 
						case "/part":
 | 
				
			||||||
		fallthrough
 | 
							fallthrough
 | 
				
			||||||
	case "/leave":
 | 
						case "/leave":
 | 
				
			||||||
		view.matrix.client.LeaveRoom(room)
 | 
							debug.Print(view.matrix.LeaveRoom(room))
 | 
				
			||||||
	case "/join":
 | 
						case "/join":
 | 
				
			||||||
		if len(args) == 0 {
 | 
							if len(args) == 0 {
 | 
				
			||||||
			view.AddServiceMessage(room, "Usage: /join <room>")
 | 
								view.AddServiceMessage(room, "Usage: /join <room>")
 | 
				
			||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		view.debug.Print(view.matrix.JoinRoom(args[0]))
 | 
							debug.Print(view.matrix.JoinRoom(args[0]))
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		view.AddServiceMessage(room, "Unknown command.")
 | 
							view.AddServiceMessage(room, "Unknown command.")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -218,7 +220,7 @@ func (view *MainView) addRoom(index int, room string) {
 | 
				
			|||||||
		view.SwitchRoom(index)
 | 
							view.SwitchRoom(index)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if !view.roomView.HasPage(room) {
 | 
						if !view.roomView.HasPage(room) {
 | 
				
			||||||
		roomView := NewRoomView(view, roomStore)
 | 
							roomView := widget.NewRoomView(view, roomStore)
 | 
				
			||||||
		view.rooms[room] = roomView
 | 
							view.rooms[room] = roomView
 | 
				
			||||||
		view.roomView.AddPage(room, roomView, true, false)
 | 
							view.roomView.AddPage(room, roomView, true, false)
 | 
				
			||||||
		roomView.UpdateUserList()
 | 
							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]
 | 
						return view.rooms[id]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -265,11 +267,11 @@ func (view *MainView) RemoveRoom(room string) {
 | 
				
			|||||||
	view.Render()
 | 
						view.Render()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MainView) SetRoomList(rooms []string) {
 | 
					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]*RoomView)
 | 
						view.rooms = make(map[string]*widget.RoomView)
 | 
				
			||||||
	for index, room := range rooms {
 | 
						for index, room := range rooms {
 | 
				
			||||||
		view.addRoom(index, room)
 | 
							view.addRoom(index, room)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -289,7 +291,7 @@ func (view *MainView) AddServiceMessage(room, message string) {
 | 
				
			|||||||
	if ok {
 | 
						if ok {
 | 
				
			||||||
		messageView := roomView.MessageView()
 | 
							messageView := roomView.MessageView()
 | 
				
			||||||
		message := messageView.NewMessage("", "*", message, time.Now())
 | 
							message := messageView.NewMessage("", "*", message, time.Now())
 | 
				
			||||||
		messageView.AddMessage(message, AppendMessage)
 | 
							messageView.AddMessage(message, widget.AppendMessage)
 | 
				
			||||||
		view.parent.Render()
 | 
							view.parent.Render()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -300,29 +302,29 @@ func (view *MainView) Render() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (view *MainView) GetHistory(room string) {
 | 
					func (view *MainView) GetHistory(room string) {
 | 
				
			||||||
	roomView := view.rooms[room]
 | 
						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 {
 | 
						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
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, evt := range history {
 | 
						for _, evt := range history {
 | 
				
			||||||
		var room *RoomView
 | 
							var room *widget.RoomView
 | 
				
			||||||
		var message *Message
 | 
							var message *types.Message
 | 
				
			||||||
		if evt.Type == "m.room.message" {
 | 
							if evt.Type == "m.room.message" {
 | 
				
			||||||
			room, message = view.ProcessMessageEvent(&evt)
 | 
								room, message = view.ProcessMessageEvent(&evt)
 | 
				
			||||||
		} else if evt.Type == "m.room.member" {
 | 
							} else if evt.Type == "m.room.member" {
 | 
				
			||||||
			room, message = view.ProcessMembershipEvent(&evt, false)
 | 
								room, message = view.ProcessMembershipEvent(&evt, false)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if room != nil && message != nil {
 | 
							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)
 | 
						room = view.GetRoom(evt.RoomID)
 | 
				
			||||||
	if room != nil {
 | 
						if room != nil {
 | 
				
			||||||
		text := evt.Content["body"].(string)
 | 
							text, _ := evt.Content["body"].(string)
 | 
				
			||||||
		message = room.NewMessage(evt.ID, evt.Sender, text, unixToTime(evt.Timestamp))
 | 
							message = room.NewMessage(evt.ID, evt.Sender, text, unixToTime(evt.Timestamp))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return
 | 
						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 {
 | 
						if new && evt.StateKey != nil && *evt.StateKey == view.config.Session.MXID {
 | 
				
			||||||
		view.processOwnMembershipChange(evt)
 | 
							view.processOwnMembershipChange(evt)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -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 main
 | 
					package widget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"math"
 | 
						"math"
 | 
				
			||||||
@@ -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 main
 | 
					package widget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"github.com/gdamore/tcell"
 | 
						"github.com/gdamore/tcell"
 | 
				
			||||||
							
								
								
									
										32
									
								
								ui/widget/center.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								ui/widget/center.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								ui/widget/color.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ui/widget/color.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -14,24 +14,13 @@
 | 
				
			|||||||
// 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 main
 | 
					package widget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"github.com/gdamore/tcell"
 | 
						"github.com/gdamore/tcell"
 | 
				
			||||||
	"maunium.net/go/tview"
 | 
						"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 {
 | 
					type FormTextView struct {
 | 
				
			||||||
	*tview.TextView
 | 
						*tview.TextView
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -14,79 +14,19 @@
 | 
				
			|||||||
// 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 main
 | 
					package widget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"regexp"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gdamore/tcell"
 | 
						"github.com/gdamore/tcell"
 | 
				
			||||||
	"github.com/mattn/go-runewidth"
 | 
						"github.com/mattn/go-runewidth"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/types"
 | 
				
			||||||
	"maunium.net/go/tview"
 | 
						"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 {
 | 
					type MessageView struct {
 | 
				
			||||||
	*tview.Box
 | 
						*tview.Box
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -106,7 +46,7 @@ type MessageView struct {
 | 
				
			|||||||
	totalHeight         int
 | 
						totalHeight         int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	messageIDs map[string]bool
 | 
						messageIDs map[string]bool
 | 
				
			||||||
	messages   []*Message
 | 
						messages   []*types.Message
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewMessageView() *MessageView {
 | 
					func NewMessageView() *MessageView {
 | 
				
			||||||
@@ -119,7 +59,7 @@ func NewMessageView() *MessageView {
 | 
				
			|||||||
		Separator:       '|',
 | 
							Separator:       '|',
 | 
				
			||||||
		ScrollOffset:    0,
 | 
							ScrollOffset:    0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		messages:   make([]*Message, 0),
 | 
							messages:   make([]*types.Message, 0),
 | 
				
			||||||
		messageIDs: make(map[string]bool),
 | 
							messageIDs: make(map[string]bool),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		widestSender:        5,
 | 
							widestSender:        5,
 | 
				
			||||||
@@ -132,11 +72,11 @@ func NewMessageView() *MessageView {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MessageView) NewMessage(id, sender, text string, timestamp time.Time) *Message {
 | 
					func (view *MessageView) NewMessage(id, sender, text string, timestamp time.Time) *types.Message {
 | 
				
			||||||
	return NewMessage(id, sender, text,
 | 
						return types.NewMessage(id, sender, text,
 | 
				
			||||||
		timestamp.Format(view.TimestampFormat),
 | 
							timestamp.Format(view.TimestampFormat),
 | 
				
			||||||
		timestamp.Format(view.DateFormat),
 | 
							timestamp.Format(view.DateFormat),
 | 
				
			||||||
		getColor(sender))
 | 
							GetHashColor(sender))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MessageView) recalculateBuffers() {
 | 
					func (view *MessageView) recalculateBuffers() {
 | 
				
			||||||
@@ -144,7 +84,7 @@ func (view *MessageView) recalculateBuffers() {
 | 
				
			|||||||
	width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
 | 
						width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
 | 
				
			||||||
	if width != view.prevWidth {
 | 
						if width != view.prevWidth {
 | 
				
			||||||
		for _, message := range view.messages {
 | 
							for _, message := range view.messages {
 | 
				
			||||||
			message.calculateBuffer(width)
 | 
								message.CalculateBuffer(width)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		view.prevWidth = width
 | 
							view.prevWidth = width
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -164,7 +104,7 @@ const (
 | 
				
			|||||||
	PrependMessage
 | 
						PrependMessage
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *MessageView) AddMessage(message *Message, direction int) {
 | 
					func (view *MessageView) AddMessage(message *types.Message, direction int) {
 | 
				
			||||||
	_, messageExists := view.messageIDs[message.ID]
 | 
						_, messageExists := view.messageIDs[message.ID]
 | 
				
			||||||
	if messageExists {
 | 
						if messageExists {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -174,15 +114,15 @@ func (view *MessageView) AddMessage(message *Message, direction int) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	_, _, 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 == AppendMessage {
 | 
				
			||||||
		if view.ScrollOffset > 0 {
 | 
							if view.ScrollOffset > 0 {
 | 
				
			||||||
			view.ScrollOffset += len(message.buffer)
 | 
								view.ScrollOffset += len(message.Buffer)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		view.messages = append(view.messages, message)
 | 
							view.messages = append(view.messages, message)
 | 
				
			||||||
	} else if direction == PrependMessage {
 | 
						} else if direction == PrependMessage {
 | 
				
			||||||
		view.messages = append([]*Message{message}, view.messages...)
 | 
							view.messages = append([]*types.Message{message}, view.messages...)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	view.messageIDs[message.ID] = true
 | 
						view.messageIDs[message.ID] = true
 | 
				
			||||||
@@ -199,7 +139,7 @@ func (view *MessageView) recalculateHeight() {
 | 
				
			|||||||
		for i := len(view.messages) - 1; i >= 0; i-- {
 | 
							for i := len(view.messages) - 1; i >= 0; i-- {
 | 
				
			||||||
			prevTotalHeight := view.totalHeight
 | 
								prevTotalHeight := view.totalHeight
 | 
				
			||||||
			message := view.messages[i]
 | 
								message := view.messages[i]
 | 
				
			||||||
			view.totalHeight += len(message.buffer)
 | 
								view.totalHeight += len(message.Buffer)
 | 
				
			||||||
			if message.Date != prevDate {
 | 
								if message.Date != prevDate {
 | 
				
			||||||
				if len(prevDate) != 0 {
 | 
									if len(prevDate) != 0 {
 | 
				
			||||||
					view.totalHeight++
 | 
										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))
 | 
								screen.SetContent(x+offsetX+localOffset, y, ch, nil, tcell.StyleDefault.Foreground(color))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		offsetX += chWidth
 | 
							offsetX += chWidth
 | 
				
			||||||
 | 
							if offsetX > maxWidth {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -302,7 +245,7 @@ func (view *MessageView) Draw(screen tcell.Screen) {
 | 
				
			|||||||
	prevSenderLine := -1
 | 
						prevSenderLine := -1
 | 
				
			||||||
	for i := view.firstDisplayMessage; i >= view.lastDisplayMessage; i-- {
 | 
						for i := view.firstDisplayMessage; i >= view.lastDisplayMessage; i-- {
 | 
				
			||||||
		message := view.messages[i]
 | 
							message := view.messages[i]
 | 
				
			||||||
		messageHeight := len(message.buffer)
 | 
							messageHeight := len(message.Buffer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Show message when the date changes.
 | 
							// Show message when the date changes.
 | 
				
			||||||
		if message.Date != prevDate {
 | 
							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.writeLine(screen, message.Timestamp, x, senderAtLine, tcell.ColorDefault)
 | 
				
			||||||
		view.writeLineRight(screen, message.Sender,
 | 
							view.writeLineRight(screen, message.Sender,
 | 
				
			||||||
			x+usernameOffsetX, senderAtLine,
 | 
								x+usernameOffsetX, senderAtLine,
 | 
				
			||||||
			view.widestSender, message.senderColor)
 | 
								view.widestSender, message.SenderColor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if message.Sender == prevSender {
 | 
							if message.Sender == prevSender {
 | 
				
			||||||
			// Sender is same as previous. We're looping from bottom to top, and we want the
 | 
								// 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.
 | 
								// below.
 | 
				
			||||||
			view.writeLineRight(screen, strings.Repeat(" ", view.widestSender),
 | 
								view.writeLineRight(screen, strings.Repeat(" ", view.widestSender),
 | 
				
			||||||
				x+usernameOffsetX, prevSenderLine,
 | 
									x+usernameOffsetX, prevSenderLine,
 | 
				
			||||||
				view.widestSender, message.senderColor)
 | 
									view.widestSender, message.SenderColor)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		prevSender = message.Sender
 | 
							prevSender = message.Sender
 | 
				
			||||||
		prevSenderLine = senderAtLine
 | 
							prevSenderLine = senderAtLine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for num, line := range message.buffer {
 | 
							for num, line := range message.Buffer {
 | 
				
			||||||
			offsetY := height - messageHeight - writeOffset + num
 | 
								offsetY := height - messageHeight - writeOffset + num
 | 
				
			||||||
			// Only render message if it's within the message view.
 | 
								// Only render message if it's within the message view.
 | 
				
			||||||
			if offsetY >= 0 {
 | 
								if offsetY >= 0 {
 | 
				
			||||||
@@ -14,19 +14,23 @@
 | 
				
			|||||||
// 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 main
 | 
					package widget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"hash/fnv"
 | 
					 | 
				
			||||||
	"sort"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gdamore/tcell"
 | 
						"github.com/gdamore/tcell"
 | 
				
			||||||
 | 
						rooms "maunium.net/go/gomuks/matrix/room"
 | 
				
			||||||
 | 
						"maunium.net/go/gomuks/ui/types"
 | 
				
			||||||
	"maunium.net/go/tview"
 | 
						"maunium.net/go/tview"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Renderable interface {
 | 
				
			||||||
 | 
						Render()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type RoomView struct {
 | 
					type RoomView struct {
 | 
				
			||||||
	*tview.Box
 | 
						*tview.Box
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,31 +38,19 @@ type RoomView struct {
 | 
				
			|||||||
	content  *MessageView
 | 
						content  *MessageView
 | 
				
			||||||
	status   *tview.TextView
 | 
						status   *tview.TextView
 | 
				
			||||||
	userList *tview.TextView
 | 
						userList *tview.TextView
 | 
				
			||||||
	room     *Room
 | 
						Room     *rooms.Room
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	parent *MainView
 | 
						parent Renderable
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var colorNames []string
 | 
					func NewRoomView(parent Renderable, room *rooms.Room) *RoomView {
 | 
				
			||||||
 | 
					 | 
				
			||||||
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 NewRoomView(parent *MainView, room *Room) *RoomView {
 | 
					 | 
				
			||||||
	view := &RoomView{
 | 
						view := &RoomView{
 | 
				
			||||||
		Box:      tview.NewBox(),
 | 
							Box:      tview.NewBox(),
 | 
				
			||||||
		topic:    tview.NewTextView(),
 | 
							topic:    tview.NewTextView(),
 | 
				
			||||||
		content:  NewMessageView(),
 | 
							content:  NewMessageView(),
 | 
				
			||||||
		status:   tview.NewTextView(),
 | 
							status:   tview.NewTextView(),
 | 
				
			||||||
		userList: tview.NewTextView(),
 | 
							userList: tview.NewTextView(),
 | 
				
			||||||
		room:     room,
 | 
							Room:     room,
 | 
				
			||||||
		parent:   parent,
 | 
							parent:   parent,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	view.topic.
 | 
						view.topic.
 | 
				
			||||||
@@ -90,9 +82,13 @@ func (view *RoomView) Draw(screen tcell.Screen) {
 | 
				
			|||||||
	view.userList.Draw(screen)
 | 
						view.userList.Draw(screen)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (view *RoomView) SetStatus(status string) {
 | 
				
			||||||
 | 
						view.status.SetText(status)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *RoomView) SetTyping(users []string) {
 | 
					func (view *RoomView) SetTyping(users []string) {
 | 
				
			||||||
	for index, user := range users {
 | 
						for index, user := range users {
 | 
				
			||||||
		member := view.room.GetMember(user)
 | 
							member := view.Room.GetMember(user)
 | 
				
			||||||
		if member != nil {
 | 
							if member != nil {
 | 
				
			||||||
			users[index] = member.DisplayName
 | 
								users[index] = member.DisplayName
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -109,7 +105,7 @@ func (view *RoomView) SetTyping(users []string) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *RoomView) AutocompleteUser(existingText string) (completions []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) {
 | 
							if strings.HasPrefix(user.DisplayName, existingText) {
 | 
				
			||||||
			completions = append(completions, user.DisplayName)
 | 
								completions = append(completions, user.DisplayName)
 | 
				
			||||||
		} else if strings.HasPrefix(user.UserID, existingText) {
 | 
							} else if strings.HasPrefix(user.UserID, existingText) {
 | 
				
			||||||
@@ -123,38 +119,15 @@ func (view *RoomView) MessageView() *MessageView {
 | 
				
			|||||||
	return view.content
 | 
						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() {
 | 
					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(color(user.DisplayName))
 | 
								joined.WriteString(AddHashColor(user.DisplayName))
 | 
				
			||||||
			joined.WriteRune('\n')
 | 
								joined.WriteRune('\n')
 | 
				
			||||||
		} else if user.Membership == "invite" {
 | 
							} else if user.Membership == "invite" {
 | 
				
			||||||
			invited.WriteString(color(user.DisplayName))
 | 
								invited.WriteString(AddHashColor(user.DisplayName))
 | 
				
			||||||
			invited.WriteRune('\n')
 | 
								invited.WriteRune('\n')
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -165,15 +138,15 @@ func (view *RoomView) UpdateUserList() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (view *RoomView) NewMessage(id, sender, text string, timestamp time.Time) *Message {
 | 
					func (view *RoomView) NewMessage(id, sender, text string, timestamp time.Time) *types.Message {
 | 
				
			||||||
	member := view.room.GetMember(sender)
 | 
						member := view.Room.GetMember(sender)
 | 
				
			||||||
	if member != nil {
 | 
						if member != nil {
 | 
				
			||||||
		sender = member.DisplayName
 | 
							sender = member.DisplayName
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return view.content.NewMessage(id, sender, text, timestamp)
 | 
						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.content.AddMessage(message, direction)
 | 
				
			||||||
	view.parent.Render()
 | 
						view.parent.Render()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user