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
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) {
+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+}