diff options
author | Hubert Hirtz <hubert@hirtzfr.eu> | 2020-09-02 00:06:37 +0200 |
---|---|---|
committer | Hubert Hirtz <hubert@hirtzfr.eu> | 2020-09-02 16:00:57 +0200 |
commit | 3ea19ff21e5cda8afca7f8114e18466e9cf7663e (patch) | |
tree | 833a97485ebd98a2d1fd805db6f3eeacaf30127a | |
parent | irc: Reset typing ratelimiter after sent message (diff) |
Typing indicator timeout
-rw-r--r-- | app.go | 11 | ||||
-rw-r--r-- | irc/states.go | 34 | ||||
-rw-r--r-- | irc/typing.go | 64 | ||||
-rw-r--r-- | ui/buffers.go | 63 | ||||
-rw-r--r-- | ui/ui.go | 8 | ||||
-rw-r--r-- | window.go | 22 |
6 files changed, 125 insertions, 77 deletions
@@ -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) { @@ -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 { @@ -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) +} |