~taiite/public-inbox

senpai: Track user away state and show away users in grey v1 NEEDS REVISION

delthas: 1
 Track user away state and show away users in grey

 7 files changed, 86 insertions(+), 32 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~taiite/public-inbox/patches/23853/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH senpai] Track user away state and show away users in grey Export this patch

---
 app.go         |  4 ++-
 commands.go    |  2 +-
 completions.go |  2 +-
 irc/events.go  |  6 ++++
 irc/session.go | 77 +++++++++++++++++++++++++++++++++++++++++++-------
 irc/tokens.go  | 22 +++------------
 ui/buffers.go  |  5 +++-
 7 files changed, 86 insertions(+), 32 deletions(-)

diff --git a/app.go b/app.go
index aed5249..37e1265 100644
--- a/app.go
+++ b/app.go
@@ -119,7 +119,7 @@ func (app *App) eventLoop() {
			app.updatePrompt()
			var currentMembers []irc.Member
			if app.s != nil {
				currentMembers = app.s.Names(app.win.CurrentBuffer())
				currentMembers = app.s.Members(app.win.CurrentBuffer())
			}
			app.win.Draw(currentMembers)
		}
@@ -630,6 +630,8 @@ func (app *App) handleIRCEvent(ev interface{}) {
			Head: head,
			Body: ui.PlainString(body),
		})
	case irc.UserAwayEvent:
		// nothing to do, we redraw after this event
	}
}

diff --git a/commands.go b/commands.go
index 626ba00..b64140f 100644
--- a/commands.go
+++ b/commands.go
@@ -264,7 +264,7 @@ func commandDoNames(app *App, buffer string, args []string) (err error) {
	sb := new(ui.StyledStringBuilder)
	sb.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGrey))
	sb.WriteString("Names: ")
	for _, name := range app.s.Names(buffer) {
	for _, name := range app.s.Members(buffer) {
		if name.PowerLevel != "" {
			sb.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGreen))
			sb.WriteString(name.PowerLevel)
diff --git a/completions.go b/completions.go
index db8a7f7..69a55fc 100644
--- a/completions.go
+++ b/completions.go
@@ -19,7 +19,7 @@ func (app *App) completionsChannelMembers(cs []ui.Completion, cursorIdx int, tex
		return cs
	}
	wordCf := app.s.Casemap(string(word))
	for _, name := range app.s.Names(app.win.CurrentBuffer()) {
	for _, name := range app.s.Members(app.win.CurrentBuffer()) {
		if strings.HasPrefix(app.s.Casemap(name.Name.Name), wordCf) {
			nickComp := []rune(name.Name.Name)
			if start == 0 {
diff --git a/irc/events.go b/irc/events.go
index eeac8fb..44d04e5 100644
--- a/irc/events.go
+++ b/irc/events.go
@@ -46,6 +46,12 @@ type UserQuitEvent struct {
	Channels []string
}

type UserAwayEvent struct {
	User    string
	Away    bool
	AwayMsg string
}

type TopicChangeEvent struct {
	Channel string
	Topic   string
diff --git a/irc/session.go b/irc/session.go
index d08fb22..2357a24 100644
--- a/irc/session.go
+++ b/irc/session.go
@@ -76,7 +76,8 @@ const (
// User is a known IRC user (we share a channel with it).
type User struct {
	Name    *Prefix // the nick, user and hostname of the user if known.
	AwayMsg string  // the away message if the user is away, "" otherwise.
	Away    bool
	AwayMsg string // the away message if the user is away and the message is known, "" otherwise.
}

// Channel is a joined channel.
@@ -91,6 +92,27 @@ type Channel struct {
	complete bool // whether this stucture is fully initialized.
}

type Member struct {
	PowerLevel string
	Name       *Prefix
	Away       bool
	AwayMsg    string
}

type members []Member

func (m members) Len() int {
	return len(m)
}

func (m members) Less(i, j int) bool {
	return strings.ToLower(m[i].Name.Name) < strings.ToLower(m[j].Name.Name)
}

func (m members) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}

// SessionParams defines how to connect to an IRC server.
type SessionParams struct {
	Nickname string
@@ -211,22 +233,24 @@ func (s *Session) Users() []string {
	return users
}

// Names returns the list of users in the given channel, or nil if this channel
// Members returns the list of users in the given channel, or nil if this channel
// is not known by the session.
// The list is sorted according to member name.
func (s *Session) Names(channel string) []Member {
	var names []Member
func (s *Session) Members(channel string) []Member {
	var m []Member
	if c, ok := s.channels[s.Casemap(channel)]; ok {
		names = make([]Member, 0, len(c.Members))
		m = make([]Member, 0, len(c.Members))
		for u, pl := range c.Members {
			names = append(names, Member{
			m = append(m, Member{
				PowerLevel: pl,
				Name:       u.Name.Copy(),
				Away:       u.Away,
				AwayMsg:    u.AwayMsg,
			})
		}
	}
	sort.Sort(members(names))
	return names
	sort.Sort(members(m))
	return m
}

// Typings returns the list of nickname who are currently typing.
@@ -539,8 +563,19 @@ func (s *Session) handleRegistered(msg Message) Event {
	case rplIsupport:
		s.updateFeatures(msg.Params[1 : len(msg.Params)-1])
	case rplWhoreply:
		if s.nickCf == s.Casemap(msg.Params[5]) {
		nick := msg.Params[5]
		if s.nickCf == s.Casemap(nick) {
			s.host = msg.Params[3]
		} else {
			nickCf := s.Casemap(nick)
			away := strings.HasPrefix(msg.Params[6], "G")
			if u, ok := s.users[nickCf]; ok {
				u.Away = away
				if !away {
					u.AwayMsg = ""
				}
				// TODO: do we want to a WHOIS on each user who is away? likely not (flood)
			}
		}
	case "CAP":
		switch msg.Params[1] {
@@ -592,6 +627,8 @@ func (s *Session) handleRegistered(msg Message) Event {
				Name:    msg.Params[0],
				Members: map[*User]string{},
			}
			// send WHO to know who's AWAY
			s.out <- NewMessage("WHO", msg.Params[0])
		} else if c, ok := s.channels[channelCf]; ok {
			if _, ok := s.users[nickCf]; !ok {
				s.users[nickCf] = &User{Name: msg.Prefix.Copy()}
@@ -668,6 +705,26 @@ func (s *Session) handleRegistered(msg Message) Event {
				Channels: channels,
			}
		}
	case "AWAY":
		nickCf := s.Casemap(msg.Prefix.Name)

		var away bool
		var awayMsg string
		if len(msg.Params) > 0 && len(msg.Params[0]) > 0 {
			away = true
			awayMsg = msg.Params[0]
		}

		if u, ok := s.users[nickCf]; ok {
			u.Away = away
			u.AwayMsg = awayMsg

			return UserAwayEvent{
				User:    u.Name.Name,
				Away:    u.Away,
				AwayMsg: u.AwayMsg,
			}
		}
	case rplNamreply:
		channelCf := s.Casemap(msg.Params[2])

@@ -685,7 +742,7 @@ func (s *Session) handleRegistered(msg Message) Event {

			s.channels[channelCf] = c
		}
	case rplEndofnames:
	case rplEndofwho:
		channelCf := s.Casemap(msg.Params[1])
		if c, ok := s.channels[channelCf]; ok && !c.complete {
			c.complete = true
diff --git a/irc/tokens.go b/irc/tokens.go
index aa5d4b6..c738c34 100644
--- a/irc/tokens.go
+++ b/irc/tokens.go
@@ -511,36 +511,22 @@ func ParseCaps(caps string) (diff []Cap) {
	return
}

// Member is a token in RPL_NAMREPLY's last parameter.
type Member struct {
// Name is a token in RPL_NAMREPLY's last parameter.
type Name struct {
	PowerLevel string
	Name       *Prefix
}

type members []Member

func (m members) Len() int {
	return len(m)
}

func (m members) Less(i, j int) bool {
	return strings.ToLower(m[i].Name.Name) < strings.ToLower(m[j].Name.Name)
}

func (m members) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}

// ParseNameReply parses the last parameter of RPL_NAMREPLY, according to the
// membership prefixes of the server.
func ParseNameReply(trailing string, prefixes string) (names []Member) {
func ParseNameReply(trailing string, prefixes string) (names []Name) {
	for _, word := range strings.Split(trailing, " ") {
		if word == "" {
			continue
		}

		name := strings.TrimLeft(word, prefixes)
		names = append(names, Member{
		names = append(names, Name{
			PowerLevel: word[:len(word)-len(name)],
			Name:       ParsePrefix(name),
		})
diff --git a/ui/buffers.go b/ui/buffers.go
index 3f2ae4a..24a9bcf 100644
--- a/ui/buffers.go
+++ b/ui/buffers.go
@@ -448,7 +448,7 @@ func (bs *BufferList) DrawVerticalMemberList(screen tcell.Screen, x0, y0, width,
	}

	for i, m := range members {
		st = tcell.StyleDefault
		st := tcell.StyleDefault
		x := x0 + 1
		y := y0 + i

@@ -458,6 +458,9 @@ func (bs *BufferList) DrawVerticalMemberList(screen tcell.Screen, x0, y0, width,
			x += 1
		}
		name := truncate(m.Name.Name, width-(x-x0), "\u2026")
		if m.Away {
			st = st.Foreground(tcell.ColorGrey)
		}
		printString(screen, &x, y, Styled(name, st))
		y++
	}
-- 
2.17.1
Thanks! Overall looks good. Below are some questions:


On Thu, 15 Jul 2021 17:13:54 +0200, delthas wrote: