Themes are working, and /theme command.
This commit is contained in:
parent
f3a4045ed9
commit
4c5dff7960
|
@ -48,7 +48,6 @@ func (ch *Channel) Close() {
|
|||
|
||||
// Handle a message, will block until done.
|
||||
func (ch *Channel) HandleMsg(m Message) {
|
||||
logger.Printf("ch.HandleMsg(%v)", m)
|
||||
switch m := m.(type) {
|
||||
case *CommandMsg:
|
||||
cmd := *m
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
@ -22,8 +21,6 @@ func TestChannelServe(t *testing.T) {
|
|||
func TestChannelJoin(t *testing.T) {
|
||||
var expected, actual []byte
|
||||
|
||||
SetLogger(os.Stderr)
|
||||
|
||||
s := &MockScreen{}
|
||||
u := NewUser("foo")
|
||||
|
||||
|
|
|
@ -134,5 +134,30 @@ func init() {
|
|||
})
|
||||
c.Alias("/names", "/list")
|
||||
|
||||
c.Add("/theme", "[mono|colors] - Set your color theme.", func(channel *Channel, msg CommandMsg) error {
|
||||
user := msg.From()
|
||||
args := msg.Args()
|
||||
if len(args) == 0 {
|
||||
theme := "plain"
|
||||
if user.Config.Theme != nil {
|
||||
theme = user.Config.Theme.Id()
|
||||
}
|
||||
body := fmt.Sprintf("Current theme: %s", theme)
|
||||
channel.Send(NewSystemMsg(body, user))
|
||||
return nil
|
||||
}
|
||||
|
||||
id := args[0]
|
||||
for _, t := range Themes {
|
||||
if t.Id() == id {
|
||||
user.Config.Theme = &t
|
||||
body := fmt.Sprintf("Set theme: %s", id)
|
||||
channel.Send(NewSystemMsg(body, user))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("theme not found")
|
||||
})
|
||||
|
||||
defaultCmdHandlers = c
|
||||
}
|
||||
|
|
|
@ -44,11 +44,11 @@ type Msg struct {
|
|||
func (m *Msg) Render(t *Theme) string {
|
||||
// TODO: Render based on theme
|
||||
// TODO: Cache based on theme
|
||||
return m.body
|
||||
return m.String()
|
||||
}
|
||||
|
||||
func (m *Msg) String() string {
|
||||
return m.Render(nil)
|
||||
return m.body
|
||||
}
|
||||
|
||||
func (m *Msg) Command() string {
|
||||
|
@ -94,11 +94,15 @@ func (m *PublicMsg) ParseCommand() (*CommandMsg, bool) {
|
|||
}
|
||||
|
||||
func (m *PublicMsg) Render(t *Theme) string {
|
||||
return fmt.Sprintf("%s: %s", m.from.Name(), m.body)
|
||||
if t == nil {
|
||||
return m.String()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s: %s", t.ColorName(m.from), m.body)
|
||||
}
|
||||
|
||||
func (m *PublicMsg) String() string {
|
||||
return m.Render(nil)
|
||||
return fmt.Sprintf("%s: %s", m.from.Name(), m.body)
|
||||
}
|
||||
|
||||
// EmoteMsg is a /me message sent to the channel. It specifically does not
|
||||
|
|
|
@ -35,7 +35,7 @@ type Color interface {
|
|||
}
|
||||
|
||||
// 256 color type, for terminals who support it
|
||||
type Color256 int8
|
||||
type Color256 uint8
|
||||
|
||||
// String version of this color
|
||||
func (c Color256) String() string {
|
||||
|
@ -68,7 +68,19 @@ type Palette struct {
|
|||
|
||||
// Get a color by index, overflows are looped around.
|
||||
func (p Palette) Get(i int) Color {
|
||||
return p.colors[i%p.size]
|
||||
return p.colors[i%(p.size-1)]
|
||||
}
|
||||
|
||||
func (p Palette) Len() int {
|
||||
return p.size
|
||||
}
|
||||
|
||||
func (p Palette) String() string {
|
||||
r := ""
|
||||
for _, c := range p.colors {
|
||||
r += c.Format("X")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Collection of settings for chat
|
||||
|
@ -79,13 +91,17 @@ type Theme struct {
|
|||
names *Palette
|
||||
}
|
||||
|
||||
// Colorize name string given some index
|
||||
func (t Theme) ColorName(s string, i int) string {
|
||||
if t.names == nil {
|
||||
return s
|
||||
func (t Theme) Id() string {
|
||||
return t.id
|
||||
}
|
||||
|
||||
return t.names.Get(i).Format(s)
|
||||
// Colorize name string given some index
|
||||
func (t Theme) ColorName(u *User) string {
|
||||
if t.names == nil {
|
||||
return u.name
|
||||
}
|
||||
|
||||
return t.names.Get(u.colorIdx).Format(u.name)
|
||||
}
|
||||
|
||||
// Colorize the PM string
|
||||
|
@ -113,16 +129,19 @@ var Themes []Theme
|
|||
var DefaultTheme *Theme
|
||||
|
||||
func readableColors256() *Palette {
|
||||
size := 247
|
||||
p := Palette{
|
||||
colors: make([]Color, 246),
|
||||
size: 246,
|
||||
colors: make([]Color, size),
|
||||
size: size,
|
||||
}
|
||||
j := 0
|
||||
for i := 0; i < 256; i++ {
|
||||
if (16 <= i && i <= 18) || (232 <= i && i <= 237) {
|
||||
// Remove the ones near black, this is kinda sadpanda.
|
||||
continue
|
||||
}
|
||||
p.colors = append(p.colors, Color256(i))
|
||||
p.colors[j] = Color256(i)
|
||||
j++
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
@ -134,6 +153,8 @@ func init() {
|
|||
Theme{
|
||||
id: "colors",
|
||||
names: palette,
|
||||
sys: palette.Get(8), // Grey
|
||||
pm: palette.Get(7), // White
|
||||
},
|
||||
Theme{
|
||||
id: "mono",
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestThemePalette(t *testing.T) {
|
||||
var expected, actual string
|
||||
|
||||
palette := readableColors256()
|
||||
color := palette.Get(5)
|
||||
if color == nil {
|
||||
t.Fatal("Failed to return a color from palette.")
|
||||
}
|
||||
|
||||
actual = color.String()
|
||||
expected = "38;05;5"
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
actual = color.Format("foo")
|
||||
expected = "\033[38;05;5mfoo\033[0m"
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
actual = palette.Get(palette.Len() + 1).String()
|
||||
expected = fmt.Sprintf("38;05;%d", 2)
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTheme(t *testing.T) {
|
||||
var expected, actual string
|
||||
|
||||
colorTheme := Themes[0]
|
||||
color := colorTheme.sys
|
||||
if color == nil {
|
||||
t.Fatal("Sys color should not be empty for first theme.")
|
||||
}
|
||||
|
||||
actual = color.Format("foo")
|
||||
expected = "\033[38;05;8mfoo\033[0m"
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
actual = colorTheme.ColorSys("foo")
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
u := NewUser("foo")
|
||||
u.colorIdx = 4
|
||||
actual = colorTheme.ColorName(u)
|
||||
expected = "\033[38;05;4mfoo\033[0m"
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
msg := NewPublicMsg("hello", u)
|
||||
actual = msg.Render(&colorTheme)
|
||||
expected = "\033[38;05;4mfoo\033[0m: hello"
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
}
|
14
chat/user.go
14
chat/user.go
|
@ -44,20 +44,26 @@ func NewUserScreen(name string, screen io.Writer) *User {
|
|||
return u
|
||||
}
|
||||
|
||||
// Return unique identifier for user
|
||||
// Id of the user, a unique identifier within a set
|
||||
func (u *User) Id() Id {
|
||||
return Id(u.name)
|
||||
}
|
||||
|
||||
// Return user's name
|
||||
// Name of the user
|
||||
func (u *User) Name() string {
|
||||
return u.name
|
||||
}
|
||||
|
||||
// Return set user's name
|
||||
// SetName will change the name of the user and reset the colorIdx
|
||||
func (u *User) SetName(name string) {
|
||||
u.name = name
|
||||
u.colorIdx = rand.Int()
|
||||
u.SetColorIdx(rand.Int())
|
||||
}
|
||||
|
||||
// SetColorIdx will set the colorIdx to a specific value, primarily used for
|
||||
// testing.
|
||||
func (u *User) SetColorIdx(idx int) {
|
||||
u.colorIdx = idx
|
||||
}
|
||||
|
||||
// Return whether user is an admin
|
||||
|
|
27
host.go
27
host.go
|
@ -33,6 +33,7 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||
term.AutoCompleteCallback = h.AutoCompleteFunction
|
||||
|
||||
user := chat.NewUserScreen(name, term)
|
||||
user.Config.Theme = &chat.Themes[0]
|
||||
go func() {
|
||||
// Close term once user is closed.
|
||||
user.Wait()
|
||||
|
@ -40,10 +41,7 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||
}()
|
||||
defer user.Close()
|
||||
|
||||
refreshPrompt := func() {
|
||||
term.SetPrompt(fmt.Sprintf("[%s] ", user.Name()))
|
||||
}
|
||||
refreshPrompt()
|
||||
term.SetPrompt(GetPrompt(user))
|
||||
|
||||
err := h.channel.Join(user)
|
||||
if err != nil {
|
||||
|
@ -52,7 +50,6 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||
}
|
||||
|
||||
for {
|
||||
// TODO: Handle commands etc?
|
||||
line, err := term.ReadLine()
|
||||
if err == io.EOF {
|
||||
// Closed
|
||||
|
@ -62,13 +59,18 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||
break
|
||||
}
|
||||
m := chat.ParseInput(line, user)
|
||||
|
||||
// FIXME: Any reason to use h.channel.Send(m) instead?
|
||||
h.channel.HandleMsg(m)
|
||||
if m.Command() == "/nick" {
|
||||
|
||||
cmd := m.Command()
|
||||
if cmd == "/nick" || cmd == "/theme" {
|
||||
// Hijack /nick command to update terminal synchronously. Wouldn't
|
||||
// work if we use h.channel.Send(m) above.
|
||||
// FIXME: This is hacky, how do we improve the API to allow for this?
|
||||
refreshPrompt()
|
||||
//
|
||||
// FIXME: This is hacky, how do we improve the API to allow for
|
||||
// this? Chat module shouldn't know about terminals.
|
||||
term.SetPrompt(GetPrompt(user))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,3 +121,12 @@ func (h *Host) AutoCompleteFunction(line string, pos int, key rune) (newLine str
|
|||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// RefreshPrompt will update the terminal prompt with the latest user name.
|
||||
func GetPrompt(user *chat.User) string {
|
||||
name := user.Name()
|
||||
if user.Config.Theme != nil {
|
||||
name = user.Config.Theme.ColorName(user)
|
||||
}
|
||||
return fmt.Sprintf("[%s] ", name)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/shazow/ssh-chat/chat"
|
||||
)
|
||||
|
||||
func TestHostGetPrompt(t *testing.T) {
|
||||
var expected, actual string
|
||||
|
||||
u := chat.NewUser("foo")
|
||||
u.SetColorIdx(2)
|
||||
|
||||
actual = GetPrompt(u)
|
||||
expected = "[foo] "
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
u.Config.Theme = &chat.Themes[0]
|
||||
actual = GetPrompt(u)
|
||||
expected = "[\033[38;05;2mfoo\033[0m] "
|
||||
if actual != expected {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue