summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHubert Hirtz <hubert@hirtzfr.eu>2020-09-02 00:06:37 +0200
committerHubert Hirtz <hubert@hirtzfr.eu>2020-09-02 16:00:57 +0200
commit3ea19ff21e5cda8afca7f8114e18466e9cf7663e (patch)
tree833a97485ebd98a2d1fd805db6f3eeacaf30127a
parentirc: Reset typing ratelimiter after sent message (diff)
Typing indicator timeout
-rw-r--r--app.go11
-rw-r--r--irc/states.go34
-rw-r--r--irc/typing.go64
-rw-r--r--ui/buffers.go63
-rw-r--r--ui/ui.go8
-rw-r--r--window.go22
6 files changed, 125 insertions, 77 deletions
diff --git a/app.go b/app.go
index f22a30c..bd7816e 100644
--- a/app.go
+++ b/app.go
@@ -199,20 +199,9 @@ func (app *App) handleIRCEvent(ev irc.Event) {
if hlNotification {
app.notifyHighlight(buffer, ev.User.Name, ev.Content)
}
- app.win.TypingStop(buffer, ev.User.Name)
if !ev.TargetIsChannel && app.s.NickCf() != app.s.Casemap(ev.User.Name) {
app.lastQuery = ev.User.Name
}
- case irc.TagEvent:
- buffer := ev.Target
- if !ev.TargetIsChannel {
- buffer = Home
- }
- if ev.Typing == irc.TypingActive || ev.Typing == irc.TypingPaused {
- app.win.TypingStart(buffer, ev.User.Name)
- } else if ev.Typing == irc.TypingDone {
- app.win.TypingStop(buffer, ev.User.Name)
- }
case irc.HistoryEvent:
var lines []ui.Line
for _, m := range ev.Messages {
diff --git a/irc/states.go b/irc/states.go
index 3ba5572..e560bdf 100644
--- a/irc/states.go
+++ b/irc/states.go
@@ -139,6 +139,7 @@ type Session struct {
running atomic.Value // bool
registered bool
+ typings *Typings
typingStamps map[string]time.Time
nick string
@@ -165,6 +166,7 @@ func NewSession(conn io.ReadWriteCloser, params SessionParams) (*Session, error)
acts: make(chan action, 64),
evts: make(chan Event, 64),
debug: params.Debug,
+ typings: NewTypings(),
typingStamps: map[string]time.Time{},
nick: params.Nickname,
nickCf: CasemapASCII(params.Nickname),
@@ -267,6 +269,17 @@ func (s *Session) Names(channel string) []Member {
return names
}
+func (s *Session) Typings(target string) []string {
+ targetCf := s.Casemap(target)
+ var res []string
+ for t := range s.typings.targets {
+ if targetCf == t.Target {
+ res = append(res, s.users[t.Name].Name.Name)
+ }
+ }
+ return res
+}
+
func (s *Session) ChannelsSharedWith(name string) []string {
var user *User
if u, ok := s.users[s.Casemap(name)]; ok {
@@ -420,6 +433,13 @@ func (s *Session) run() {
} else {
err = s.handleStart(msg)
}
+ case t := <-s.typings.Stops():
+ s.evts <- TagEvent{
+ User: s.users[t.Name].Name,
+ Target: s.channels[t.Target].Name,
+ Typing: TypingDone,
+ Time: time.Now(),
+ }
}
if err != nil {
@@ -686,12 +706,12 @@ func (s *Session) handle(msg Message) (err error) {
if u, ok := s.users[nickCf]; ok {
delete(c.Members, u)
s.cleanUser(u)
- t := msg.TimeOrNow()
+ s.typings.Done(channelCf, nickCf)
s.evts <- UserPartEvent{
User: msg.Prefix.Copy(),
Channel: c.Name,
- Time: t,
+ Time: msg.TimeOrNow(),
}
}
}
@@ -699,20 +719,20 @@ func (s *Session) handle(msg Message) (err error) {
nickCf := s.Casemap(msg.Prefix.Name)
if u, ok := s.users[nickCf]; ok {
- t := msg.TimeOrNow()
var channels []string
- for _, c := range s.channels {
+ for channelCf, c := range s.channels {
if _, ok := c.Members[u]; ok {
channels = append(channels, c.Name)
delete(c.Members, u)
s.cleanUser(u)
+ s.typings.Done(channelCf, nickCf)
}
}
s.evts <- UserQuitEvent{
User: msg.Prefix.Copy(),
Channels: channels,
- Time: t,
+ Time: msg.TimeOrNow(),
}
}
case rplNamreply:
@@ -776,10 +796,13 @@ func (s *Session) handle(msg Message) (err error) {
if t, ok := msg.Tags["+typing"]; ok {
if t == "active" {
typing = TypingActive
+ s.typings.Active(targetCf, nickCf)
} else if t == "paused" {
typing = TypingPaused
+ s.typings.Active(targetCf, nickCf)
} else if t == "done" {
typing = TypingDone
+ s.typings.Done(targetCf, nickCf)
}
} else {
break
@@ -859,6 +882,7 @@ func (s *Session) handle(msg Message) (err error) {
func (s *Session) privmsgToEvent(msg Message) (ev MessageEvent) {
targetCf := s.Casemap(msg.Params[0])
+ s.typings.Done(targetCf, s.Casemap(msg.Prefix.Name))
ev = MessageEvent{
User: msg.Prefix.Copy(), // TODO correctly casemap
Target: msg.Params[0], // TODO correctly casemap
diff --git a/irc/typing.go b/irc/typing.go
new file mode 100644
index 0000000..a62a420
--- /dev/null
+++ b/irc/typing.go
@@ -0,0 +1,64 @@
+package irc
+
+import (
+ "sync"
+ "time"
+)
+
+type Typing struct {
+ Target string
+ Name string
+}
+
+type Typings struct {
+ l sync.Mutex
+ targets map[Typing]time.Time
+ timeouts chan Typing
+ stops chan Typing
+}
+
+func NewTypings() *Typings {
+ ts := &Typings{
+ targets: map[Typing]time.Time{},
+ timeouts: make(chan Typing, 16),
+ stops: make(chan Typing, 16),
+ }
+ go func() {
+ for {
+ t := <-ts.timeouts
+ now := time.Now()
+ ts.l.Lock()
+ oldT, ok := ts.targets[t]
+ if ok && 6.0 < now.Sub(oldT).Seconds() {
+ delete(ts.targets, t)
+ ts.l.Unlock()
+ ts.stops <- t
+ } else {
+ ts.l.Unlock()
+ }
+ }
+ }()
+ return ts
+}
+
+func (ts *Typings) Stops() <-chan Typing {
+ return ts.stops
+}
+
+func (ts *Typings) Active(target, name string) {
+ t := Typing{target, name}
+ ts.l.Lock()
+ ts.targets[t] = time.Now()
+ ts.l.Unlock()
+
+ go func() {
+ time.Sleep(6 * time.Second)
+ ts.timeouts <- t
+ }()
+}
+
+func (ts *Typings) Done(target, name string) {
+ ts.l.Lock()
+ delete(ts.targets, Typing{target, name})
+ ts.l.Unlock()
+}
diff --git a/ui/buffers.go b/ui/buffers.go
index 5620f60..ec89893 100644
--- a/ui/buffers.go
+++ b/ui/buffers.go
@@ -163,8 +163,7 @@ type buffer struct {
highlights int
unread bool
- lines []Line
- typings []string
+ lines []Line
scrollAmt int
isAtTop bool
@@ -246,6 +245,7 @@ func (b *buffer) DrawLines(screen tcell.Screen, width, height, nickColWidth int)
type BufferList struct {
list []buffer
current int
+ status string
width int
height int
@@ -362,36 +362,8 @@ func (bs *BufferList) AddLines(title string, lines []Line) {
b.lines = append(lines[:limit], b.lines...)
}
-func (bs *BufferList) TypingStart(title, nick string) {
- idx := bs.idx(title)
- if idx < 0 {
- return
- }
- b := &bs.list[idx]
-
- lNick := strings.ToLower(nick)
- for _, n := range b.typings {
- if strings.ToLower(n) == lNick {
- return
- }
- }
- b.typings = append(b.typings, nick)
-}
-
-func (bs *BufferList) TypingStop(title, nick string) {
- idx := bs.idx(title)
- if idx < 0 {
- return
- }
- b := &bs.list[idx]
-
- lNick := strings.ToLower(nick)
- for i, n := range b.typings {
- if strings.ToLower(n) == lNick {
- b.typings = append(b.typings[:i], b.typings[i+1:]...)
- return
- }
- }
+func (bs *BufferList) SetStatus(status string) {
+ bs.status = status
}
func (bs *BufferList) Current() (title string) {
@@ -450,38 +422,19 @@ func (bs *BufferList) Draw(screen tcell.Screen) {
func (bs *BufferList) drawStatusBar(screen tcell.Screen, y int) {
st := tcell.StyleDefault.Dim(true)
- nicks := bs.list[bs.current].typings
- verb := " is typing..."
for x := 0; x < bs.width; x++ {
screen.SetContent(x, y, 0x2500, nil, st)
}
- if len(nicks) == 0 {
+ if bs.status == "" {
return
}
- screen.SetContent(1, y, 0x2524, nil, st)
-
x := 2
- if 1 < len(nicks) {
- verb = " are typing..."
- for _, nick := range nicks[:len(nicks)-2] {
- printString(screen, &x, y, st, nick)
- printString(screen, &x, y, st, ", ")
- }
- printString(screen, &x, y, st, nicks[len(nicks)-2])
- printString(screen, &x, y, st, " and ")
- }
- if 0 < len(nicks) {
- printString(screen, &x, y, st, nicks[len(nicks)-1])
- printString(screen, &x, y, st, verb)
- }
-
- if 0 < x {
- screen.SetContent(x, y, 0x251c, nil, st)
- x++
- }
+ screen.SetContent(1, y, 0x2524, nil, st)
+ printString(screen, &x, y, st, bs.status)
+ screen.SetContent(x, y, 0x251c, nil, st)
}
func (bs *BufferList) drawTitleList(screen tcell.Screen, y int) {
diff --git a/ui/ui.go b/ui/ui.go
index dc6e73a..9d295ce 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -113,12 +113,8 @@ func (ui *UI) AddLines(buffer string, lines []Line) {
ui.bs.AddLines(buffer, lines)
}
-func (ui *UI) TypingStart(buffer, nick string) {
- ui.bs.TypingStart(buffer, nick)
-}
-
-func (ui *UI) TypingStop(buffer, nick string) {
- ui.bs.TypingStop(buffer, nick)
+func (ui *UI) SetStatus(status string) {
+ ui.bs.SetStatus(status)
}
func (ui *UI) InputIsCommand() bool {
diff --git a/window.go b/window.go
index a3b7e91..dcd3fba 100644
--- a/window.go
+++ b/window.go
@@ -2,6 +2,7 @@ package senpai
import (
"math/rand"
+ "strings"
"time"
"git.sr.ht/~taiite/senpai/ui"
@@ -36,5 +37,26 @@ func (app *App) addLineNow(buffer string, line ui.Line) {
}
func (app *App) draw() {
+ if app.s != nil {
+ app.setStatus()
+ }
app.win.Draw()
}
+
+func (app *App) setStatus() {
+ ts := app.s.Typings(app.win.CurrentBuffer())
+ status := ""
+ if 3 < len(ts) {
+ status = "several people are typing..."
+ } else {
+ verb := " is typing..."
+ if 1 < len(ts) {
+ verb = " are typing..."
+ status = strings.Join(ts[:len(ts)-1], ", ") + " and "
+ }
+ if 0 < len(ts) {
+ status += ts[len(ts)-1] + verb
+ }
+ }
+ app.win.SetStatus(status)
+}