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+}
M
ui/ui.go
+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]