commit 4266525

Hubert Hirtz  ·  2020-06-23 10:13:09 +0000 UTC
parent 81c860a
Display typing indicators
6 files changed,  +240, -11
+7, -0
 1@@ -71,6 +71,13 @@ func main() {
 2 			case irc.ChannelMessageEvent:
 3 				line := formatIRCMessage(ev.Nick, ev.Content)
 4 				app.AddLine(ev.Channel, line, ev.Time, false)
 5+				app.TypingStop(ev.Channel, ev.Nick)
 6+			case irc.ChannelTypingEvent:
 7+				if ev.State == 1 || ev.State == 2 {
 8+					app.TypingStart(ev.Channel, ev.Nick)
 9+				} else {
10+					app.TypingStop(ev.Channel, ev.Nick)
11+				}
12 			case irc.HistoryEvent:
13 				var lines []ui.Line
14 				var lastT time.Time
+13, -0
 1@@ -62,6 +62,19 @@ type ChannelMessageEvent struct {
 2 	Time    time.Time
 3 }
 4 
 5+type QueryTypingEvent struct {
 6+	UserEvent
 7+	State int
 8+	Time  time.Time
 9+}
10+
11+type ChannelTypingEvent struct {
12+	UserEvent
13+	ChannelEvent
14+	State int
15+	Time  time.Time
16+}
17+
18 type HistoryEvent struct {
19 	Target   string
20 	Messages []Event
+48, -8
 1@@ -611,6 +611,7 @@ func (s *Session) handle(msg Message) (ev Event, err error) {
 2 				continue
 3 			}
 4 
 5+			// TODO UserQuitEvent
 6 			ev = UserPartEvent{
 7 				ChannelEvent: ChannelEvent{Channel: c.Name},
 8 				UserEvent:    UserEvent{Nick: nick},
 9@@ -644,6 +645,48 @@ func (s *Session) handle(msg Message) (ev Event, err error) {
10 		}
11 	case "PRIVMSG":
12 		ev = s.privmsgToEvent(msg)
13+	case "TAGMSG":
14+		nick, _, _ := FullMask(msg.Prefix)
15+		target := strings.ToLower(msg.Params[0])
16+
17+		if strings.ToLower(nick) == s.lNick {
18+			// TAGMSG from self
19+			break
20+		}
21+
22+		typing := 0
23+		if t, ok := msg.Tags["+typing"]; ok {
24+			if t == "active" {
25+				typing = 1
26+			} else if t == "paused" {
27+				typing = 2
28+			} else if t == "done" {
29+				typing = 3
30+			}
31+		} else {
32+			break
33+		}
34+
35+		t, ok := msg.Time()
36+		if !ok {
37+			t = time.Now()
38+		}
39+		if target == s.lNick {
40+			// TAGMSG to self
41+			ev = QueryTypingEvent{
42+				UserEvent: UserEvent{Nick: nick},
43+				State:     typing,
44+				Time:      t,
45+			}
46+		} else if _, ok := s.channels[target]; ok {
47+			// TAGMSG to channel
48+			ev = ChannelTypingEvent{
49+				UserEvent:    UserEvent{Nick: nick},
50+				ChannelEvent: ChannelEvent{Channel: msg.Params[0]},
51+				State:        typing,
52+				Time:         t,
53+			}
54+		}
55 	case "BATCH":
56 		batchStart := msg.Params[0][0] == '+'
57 		id := msg.Params[0][1:]
58@@ -676,12 +719,13 @@ func (s *Session) privmsgToEvent(msg Message) (ev Event) {
59 	nick, _, _ := FullMask(msg.Prefix)
60 	target := strings.ToLower(msg.Params[0])
61 
62+	t, ok := msg.Time()
63+	if !ok {
64+		t = time.Now()
65+	}
66+
67 	if target == s.lNick {
68 		// PRIVMSG to self
69-		t, ok := msg.Time()
70-		if !ok {
71-			t = time.Now()
72-		}
73 		ev = QueryMessageEvent{
74 			UserEvent: UserEvent{Nick: nick},
75 			Content:   msg.Params[1],
76@@ -689,10 +733,6 @@ func (s *Session) privmsgToEvent(msg Message) (ev Event) {
77 		}
78 	} else if _, ok := s.channels[target]; ok {
79 		// PRIVMSG to channel
80-		t, ok := msg.Time()
81-		if !ok {
82-			t = time.Now()
83-		}
84 		ev = ChannelMessageEvent{
85 			UserEvent:    UserEvent{Nick: nick},
86 			ChannelEvent: ChannelEvent{Channel: msg.Params[0]},
+14, -0
 1@@ -220,6 +220,20 @@ func (msg *Message) Validate() (err error) {
 2 		if len(msg.Params) < 3 {
 3 			err = errNotEnoughParams
 4 		}
 5+	case "PRIVMSG":
 6+		fallthrough
 7+	case "NOTICE":
 8+		if len(msg.Params) < 2 {
 9+			err = errNotEnoughParams
10+		} else if msg.Prefix == "" {
11+			err = errNoPrefix
12+		}
13+	case "TAGMSG":
14+		if len(msg.Params) < 1 {
15+			err = errNotEnoughParams
16+		} else if msg.Prefix == "" {
17+			err = errNoPrefix
18+		}
19 	case "TOPIC":
20 		if len(msg.Params) < 2 {
21 			err = errNotEnoughParams
+24, -0
 1@@ -140,6 +140,7 @@ type Buffer struct {
 2 	Title      string
 3 	Highlights int
 4 	Content    []Line
 5+	Typings    []string
 6 }
 7 
 8 type BufferList struct {
 9@@ -282,3 +283,26 @@ func (bs *BufferList) Invalidate() {
10 		}
11 	}
12 }
13+
14+func (bs *BufferList) TypingStart(idx int, nick string) {
15+	b := &bs.List[idx]
16+
17+	for _, n := range b.Typings {
18+		if n == nick {
19+			return
20+		}
21+	}
22+
23+	b.Typings = append(b.Typings, nick)
24+}
25+
26+func (bs *BufferList) TypingStop(idx int, nick string) {
27+	b := &bs.List[idx]
28+
29+	for i, n := range b.Typings {
30+		if n == nick {
31+			b.Typings = append(b.Typings[:i], b.Typings[i+1:]...)
32+			return
33+		}
34+	}
35+}
+134, -3
  1@@ -95,6 +95,7 @@ func (ui *UI) NextBuffer() (ok bool) {
  2 	if ok {
  3 		ui.scrollAmt = 0
  4 		ui.scrollAtTop = false
  5+		ui.drawTyping()
  6 		ui.drawBuffer()
  7 		ui.drawStatus()
  8 	}
  9@@ -106,6 +107,7 @@ func (ui *UI) PreviousBuffer() (ok bool) {
 10 	if ok {
 11 		ui.scrollAmt = 0
 12 		ui.scrollAtTop = false
 13+		ui.drawTyping()
 14 		ui.drawBuffer()
 15 		ui.drawStatus()
 16 	}
 17@@ -145,6 +147,7 @@ func (ui *UI) AddBuffer(title string) {
 18 	_, ok := ui.bufferList.Add(title)
 19 	if ok {
 20 		ui.drawStatus()
 21+		ui.drawTyping()
 22 		ui.drawBuffer() // TODO only invalidate buffer list
 23 	}
 24 }
 25@@ -153,6 +156,7 @@ func (ui *UI) RemoveBuffer(title string) {
 26 	ok := ui.bufferList.Remove(title)
 27 	if ok {
 28 		ui.drawStatus()
 29+		ui.drawTyping()
 30 		ui.drawBuffer()
 31 	}
 32 }
 33@@ -174,6 +178,32 @@ func (ui *UI) AddLine(buffer string, line string, t time.Time, isStatus bool) {
 34 	}
 35 }
 36 
 37+func (ui *UI) TypingStart(buffer, nick string) {
 38+	idx := ui.bufferList.Idx(buffer)
 39+	if idx < 0 {
 40+		return
 41+	}
 42+
 43+	ui.bufferList.TypingStart(idx, nick)
 44+
 45+	if idx == ui.bufferList.Current {
 46+		ui.drawTyping()
 47+	}
 48+}
 49+
 50+func (ui *UI) TypingStop(buffer, nick string) {
 51+	idx := ui.bufferList.Idx(buffer)
 52+	if idx < 0 {
 53+		return
 54+	}
 55+
 56+	ui.bufferList.TypingStop(idx, nick)
 57+
 58+	if idx == ui.bufferList.Current {
 59+		ui.drawTyping()
 60+	}
 61+}
 62+
 63 func (ui *UI) AddHistoryLines(buffer string, lines []Line) {
 64 	idx := ui.bufferList.Idx(buffer)
 65 	if idx < 0 {
 66@@ -249,6 +279,7 @@ func (ui *UI) Resize() {
 67 func (ui *UI) draw() {
 68 	ui.drawStatus()
 69 	ui.drawEditor()
 70+	ui.drawTyping()
 71 	ui.drawBuffer()
 72 }
 73 
 74@@ -286,6 +317,106 @@ func (ui *UI) drawEditor() {
 75 	ui.screen.Show()
 76 }
 77 
 78+func (ui *UI) drawTyping() {
 79+	st := tcell.StyleDefault.Dim(true)
 80+	w, h := ui.screen.Size()
 81+	if w == 0 {
 82+		return
 83+	}
 84+
 85+	nicks := ui.bufferList.List[ui.bufferList.Current].Typings
 86+	if len(nicks) == 0 {
 87+		return
 88+	}
 89+
 90+	x := 0
 91+	y := h - 3
 92+
 93+	if 1 < len(nicks) {
 94+		for _, nick := range nicks[:len(nicks)-2] {
 95+			for _, r := range nick {
 96+				if w <= x {
 97+					return
 98+				}
 99+				ui.screen.SetContent(x, y, r, nil, st)
100+				x += runewidth.RuneWidth(r)
101+			}
102+
103+			if w <= x {
104+				return
105+			}
106+			ui.screen.SetContent(x, y, ',', nil, st)
107+			x++
108+			if w <= x {
109+				return
110+			}
111+			ui.screen.SetContent(x, y, ' ', nil, st)
112+			x++
113+		}
114+
115+		for _, r := range nicks[len(nicks)-2] {
116+			if w <= x {
117+				return
118+			}
119+			ui.screen.SetContent(x, y, r, nil, st)
120+			x += runewidth.RuneWidth(r)
121+		}
122+
123+		if w <= x {
124+			return
125+		}
126+		ui.screen.SetContent(x, y, ' ', nil, st)
127+		x++
128+		if w <= x {
129+			return
130+		}
131+		ui.screen.SetContent(x, y, 'a', nil, st)
132+		x++
133+		if w <= x {
134+			return
135+		}
136+		ui.screen.SetContent(x, y, 'n', nil, st)
137+		x++
138+		if w <= x {
139+			return
140+		}
141+		ui.screen.SetContent(x, y, 'd', nil, st)
142+		x++
143+		if w <= x {
144+			return
145+		}
146+		ui.screen.SetContent(x, y, ' ', nil, st)
147+		x++
148+	}
149+
150+	for _, r := range nicks[len(nicks)-1] {
151+		if w <= x {
152+			return
153+		}
154+		ui.screen.SetContent(x, y, r, nil, st)
155+		x += runewidth.RuneWidth(r)
156+	}
157+
158+	verb := " are typing..."
159+	if len(nicks) == 1 {
160+		verb = " is typing..."
161+	}
162+
163+	for _, r := range verb {
164+		if w <= x {
165+			return
166+		}
167+		ui.screen.SetContent(x, y, r, nil, st)
168+		x += runewidth.RuneWidth(r)
169+	}
170+
171+	for ; x < w; x++ {
172+		ui.screen.SetContent(x, y, ' ', nil, st)
173+	}
174+
175+	ui.screen.Show()
176+}
177+
178 func (ui *UI) drawBuffer() {
179 	st := tcell.StyleDefault
180 	w, h := ui.screen.Size()
181@@ -294,7 +425,7 @@ func (ui *UI) drawBuffer() {
182 	}
183 
184 	for x := 0; x < w; x++ {
185-		for y := 0; y < h-2; y++ {
186+		for y := 0; y < h-3; y++ {
187 			ui.screen.SetContent(x, y, ' ', nil, st)
188 		}
189 	}
190@@ -310,8 +441,8 @@ func (ui *UI) drawBuffer() {
191 	var colorState int
192 	var fgColor, bgColor int
193 
194-	yEnd := h - 2
195-	y0 := ui.scrollAmt + h - 2
196+	yEnd := h - 3
197+	y0 := ui.scrollAmt + h - 3
198 
199 	for i := len(b.Content) - 1; 0 <= i; i-- {
200 		line := &b.Content[i]