commit 3ea19ff
Hubert Hirtz
·
2020-09-01 22:06:37 +0000 UTC
parent 500a3f2
Typing indicator timeout
6 files changed,
+125,
-77
M
app.go
+0,
-11
1@@ -199,20 +199,9 @@ func (app *App) handleIRCEvent(ev irc.Event) {
2 if hlNotification {
3 app.notifyHighlight(buffer, ev.User.Name, ev.Content)
4 }
5- app.win.TypingStop(buffer, ev.User.Name)
6 if !ev.TargetIsChannel && app.s.NickCf() != app.s.Casemap(ev.User.Name) {
7 app.lastQuery = ev.User.Name
8 }
9- case irc.TagEvent:
10- buffer := ev.Target
11- if !ev.TargetIsChannel {
12- buffer = Home
13- }
14- if ev.Typing == irc.TypingActive || ev.Typing == irc.TypingPaused {
15- app.win.TypingStart(buffer, ev.User.Name)
16- } else if ev.Typing == irc.TypingDone {
17- app.win.TypingStop(buffer, ev.User.Name)
18- }
19 case irc.HistoryEvent:
20 var lines []ui.Line
21 for _, m := range ev.Messages {
+29,
-5
1@@ -139,6 +139,7 @@ type Session struct {
2
3 running atomic.Value // bool
4 registered bool
5+ typings *Typings
6 typingStamps map[string]time.Time
7
8 nick string
9@@ -165,6 +166,7 @@ func NewSession(conn io.ReadWriteCloser, params SessionParams) (*Session, error)
10 acts: make(chan action, 64),
11 evts: make(chan Event, 64),
12 debug: params.Debug,
13+ typings: NewTypings(),
14 typingStamps: map[string]time.Time{},
15 nick: params.Nickname,
16 nickCf: CasemapASCII(params.Nickname),
17@@ -267,6 +269,17 @@ func (s *Session) Names(channel string) []Member {
18 return names
19 }
20
21+func (s *Session) Typings(target string) []string {
22+ targetCf := s.Casemap(target)
23+ var res []string
24+ for t := range s.typings.targets {
25+ if targetCf == t.Target {
26+ res = append(res, s.users[t.Name].Name.Name)
27+ }
28+ }
29+ return res
30+}
31+
32 func (s *Session) ChannelsSharedWith(name string) []string {
33 var user *User
34 if u, ok := s.users[s.Casemap(name)]; ok {
35@@ -420,6 +433,13 @@ func (s *Session) run() {
36 } else {
37 err = s.handleStart(msg)
38 }
39+ case t := <-s.typings.Stops():
40+ s.evts <- TagEvent{
41+ User: s.users[t.Name].Name,
42+ Target: s.channels[t.Target].Name,
43+ Typing: TypingDone,
44+ Time: time.Now(),
45+ }
46 }
47
48 if err != nil {
49@@ -686,12 +706,12 @@ func (s *Session) handle(msg Message) (err error) {
50 if u, ok := s.users[nickCf]; ok {
51 delete(c.Members, u)
52 s.cleanUser(u)
53- t := msg.TimeOrNow()
54+ s.typings.Done(channelCf, nickCf)
55
56 s.evts <- UserPartEvent{
57 User: msg.Prefix.Copy(),
58 Channel: c.Name,
59- Time: t,
60+ Time: msg.TimeOrNow(),
61 }
62 }
63 }
64@@ -699,20 +719,20 @@ func (s *Session) handle(msg Message) (err error) {
65 nickCf := s.Casemap(msg.Prefix.Name)
66
67 if u, ok := s.users[nickCf]; ok {
68- t := msg.TimeOrNow()
69 var channels []string
70- for _, c := range s.channels {
71+ for channelCf, c := range s.channels {
72 if _, ok := c.Members[u]; ok {
73 channels = append(channels, c.Name)
74 delete(c.Members, u)
75 s.cleanUser(u)
76+ s.typings.Done(channelCf, nickCf)
77 }
78 }
79
80 s.evts <- UserQuitEvent{
81 User: msg.Prefix.Copy(),
82 Channels: channels,
83- Time: t,
84+ Time: msg.TimeOrNow(),
85 }
86 }
87 case rplNamreply:
88@@ -776,10 +796,13 @@ func (s *Session) handle(msg Message) (err error) {
89 if t, ok := msg.Tags["+typing"]; ok {
90 if t == "active" {
91 typing = TypingActive
92+ s.typings.Active(targetCf, nickCf)
93 } else if t == "paused" {
94 typing = TypingPaused
95+ s.typings.Active(targetCf, nickCf)
96 } else if t == "done" {
97 typing = TypingDone
98+ s.typings.Done(targetCf, nickCf)
99 }
100 } else {
101 break
102@@ -859,6 +882,7 @@ func (s *Session) handle(msg Message) (err error) {
103 func (s *Session) privmsgToEvent(msg Message) (ev MessageEvent) {
104 targetCf := s.Casemap(msg.Params[0])
105
106+ s.typings.Done(targetCf, s.Casemap(msg.Prefix.Name))
107 ev = MessageEvent{
108 User: msg.Prefix.Copy(), // TODO correctly casemap
109 Target: msg.Params[0], // TODO correctly casemap
+64,
-0
1@@ -0,0 +1,64 @@
2+package irc
3+
4+import (
5+ "sync"
6+ "time"
7+)
8+
9+type Typing struct {
10+ Target string
11+ Name string
12+}
13+
14+type Typings struct {
15+ l sync.Mutex
16+ targets map[Typing]time.Time
17+ timeouts chan Typing
18+ stops chan Typing
19+}
20+
21+func NewTypings() *Typings {
22+ ts := &Typings{
23+ targets: map[Typing]time.Time{},
24+ timeouts: make(chan Typing, 16),
25+ stops: make(chan Typing, 16),
26+ }
27+ go func() {
28+ for {
29+ t := <-ts.timeouts
30+ now := time.Now()
31+ ts.l.Lock()
32+ oldT, ok := ts.targets[t]
33+ if ok && 6.0 < now.Sub(oldT).Seconds() {
34+ delete(ts.targets, t)
35+ ts.l.Unlock()
36+ ts.stops <- t
37+ } else {
38+ ts.l.Unlock()
39+ }
40+ }
41+ }()
42+ return ts
43+}
44+
45+func (ts *Typings) Stops() <-chan Typing {
46+ return ts.stops
47+}
48+
49+func (ts *Typings) Active(target, name string) {
50+ t := Typing{target, name}
51+ ts.l.Lock()
52+ ts.targets[t] = time.Now()
53+ ts.l.Unlock()
54+
55+ go func() {
56+ time.Sleep(6 * time.Second)
57+ ts.timeouts <- t
58+ }()
59+}
60+
61+func (ts *Typings) Done(target, name string) {
62+ ts.l.Lock()
63+ delete(ts.targets, Typing{target, name})
64+ ts.l.Unlock()
65+}
+8,
-55
1@@ -163,8 +163,7 @@ type buffer struct {
2 highlights int
3 unread bool
4
5- lines []Line
6- typings []string
7+ lines []Line
8
9 scrollAmt int
10 isAtTop bool
11@@ -246,6 +245,7 @@ func (b *buffer) DrawLines(screen tcell.Screen, width, height, nickColWidth int)
12 type BufferList struct {
13 list []buffer
14 current int
15+ status string
16
17 width int
18 height int
19@@ -362,36 +362,8 @@ func (bs *BufferList) AddLines(title string, lines []Line) {
20 b.lines = append(lines[:limit], b.lines...)
21 }
22
23-func (bs *BufferList) TypingStart(title, nick string) {
24- idx := bs.idx(title)
25- if idx < 0 {
26- return
27- }
28- b := &bs.list[idx]
29-
30- lNick := strings.ToLower(nick)
31- for _, n := range b.typings {
32- if strings.ToLower(n) == lNick {
33- return
34- }
35- }
36- b.typings = append(b.typings, nick)
37-}
38-
39-func (bs *BufferList) TypingStop(title, nick string) {
40- idx := bs.idx(title)
41- if idx < 0 {
42- return
43- }
44- b := &bs.list[idx]
45-
46- lNick := strings.ToLower(nick)
47- for i, n := range b.typings {
48- if strings.ToLower(n) == lNick {
49- b.typings = append(b.typings[:i], b.typings[i+1:]...)
50- return
51- }
52- }
53+func (bs *BufferList) SetStatus(status string) {
54+ bs.status = status
55 }
56
57 func (bs *BufferList) Current() (title string) {
58@@ -450,38 +422,19 @@ func (bs *BufferList) Draw(screen tcell.Screen) {
59
60 func (bs *BufferList) drawStatusBar(screen tcell.Screen, y int) {
61 st := tcell.StyleDefault.Dim(true)
62- nicks := bs.list[bs.current].typings
63- verb := " is typing..."
64
65 for x := 0; x < bs.width; x++ {
66 screen.SetContent(x, y, 0x2500, nil, st)
67 }
68
69- if len(nicks) == 0 {
70+ if bs.status == "" {
71 return
72 }
73
74- screen.SetContent(1, y, 0x2524, nil, st)
75-
76 x := 2
77- if 1 < len(nicks) {
78- verb = " are typing..."
79- for _, nick := range nicks[:len(nicks)-2] {
80- printString(screen, &x, y, st, nick)
81- printString(screen, &x, y, st, ", ")
82- }
83- printString(screen, &x, y, st, nicks[len(nicks)-2])
84- printString(screen, &x, y, st, " and ")
85- }
86- if 0 < len(nicks) {
87- printString(screen, &x, y, st, nicks[len(nicks)-1])
88- printString(screen, &x, y, st, verb)
89- }
90-
91- if 0 < x {
92- screen.SetContent(x, y, 0x251c, nil, st)
93- x++
94- }
95+ screen.SetContent(1, y, 0x2524, nil, st)
96+ printString(screen, &x, y, st, bs.status)
97+ screen.SetContent(x, y, 0x251c, nil, st)
98 }
99
100 func (bs *BufferList) drawTitleList(screen tcell.Screen, y int) {
M
ui/ui.go
+2,
-6
1@@ -113,12 +113,8 @@ func (ui *UI) AddLines(buffer string, lines []Line) {
2 ui.bs.AddLines(buffer, lines)
3 }
4
5-func (ui *UI) TypingStart(buffer, nick string) {
6- ui.bs.TypingStart(buffer, nick)
7-}
8-
9-func (ui *UI) TypingStop(buffer, nick string) {
10- ui.bs.TypingStop(buffer, nick)
11+func (ui *UI) SetStatus(status string) {
12+ ui.bs.SetStatus(status)
13 }
14
15 func (ui *UI) InputIsCommand() bool {
+22,
-0
1@@ -2,6 +2,7 @@ package senpai
2
3 import (
4 "math/rand"
5+ "strings"
6 "time"
7
8 "git.sr.ht/~taiite/senpai/ui"
9@@ -36,5 +37,26 @@ func (app *App) addLineNow(buffer string, line ui.Line) {
10 }
11
12 func (app *App) draw() {
13+ if app.s != nil {
14+ app.setStatus()
15+ }
16 app.win.Draw()
17 }
18+
19+func (app *App) setStatus() {
20+ ts := app.s.Typings(app.win.CurrentBuffer())
21+ status := ""
22+ if 3 < len(ts) {
23+ status = "several people are typing..."
24+ } else {
25+ verb := " is typing..."
26+ if 1 < len(ts) {
27+ verb = " are typing..."
28+ status = strings.Join(ts[:len(ts)-1], ", ") + " and "
29+ }
30+ if 0 < len(ts) {
31+ status += ts[len(ts)-1] + verb
32+ }
33+ }
34+ app.win.SetStatus(status)
35+}