commit 6bb73eb
delthas
·
2024-07-18 09:06:43 +0000 UTC
parent 8d6749a
Collapse more status changes (join/part/quit/mode) Fixes: https://todo.sr.ht/~delthas/senpai/94
1 files changed,
+172,
-62
M
app.go
M
app.go
+172,
-62
1@@ -1460,6 +1460,16 @@ func (app *App) completions(cursorIdx int, text []rune) []ui.Completion {
2 return cs
3 }
4
5+type mergedEvent struct {
6+ oldNick string
7+ nick string
8+ firstConnected int // -1: offline; 1: online
9+ lastConnected int // -1: offline; 1: online
10+ modeSet string
11+ modeUnset string
12+ channelMode string
13+}
14+
15 // formatEvent returns a formatted ui.Line for an irc.Event.
16 func (app *App) formatEvent(ev irc.Event) ui.Line {
17 switch ev := ev.(type) {
18@@ -1558,8 +1568,6 @@ func (app *App) formatEvent(ev irc.Event) ui.Line {
19 }
20 case irc.ModeChangeEvent:
21 body := fmt.Sprintf("[%s]", ev.Mode)
22- // simple mode event: <+/-><mode> <nick>
23- mergeable := len(strings.Split(ev.Mode, " ")) == 2
24 return ui.Line{
25 At: ev.Time,
26 Head: "--",
27@@ -1567,10 +1575,65 @@ func (app *App) formatEvent(ev irc.Event) ui.Line {
28 Body: ui.Styled(body, vaxis.Style{
29 Foreground: app.cfg.Colors.Status,
30 }),
31- Mergeable: mergeable,
32+ Mergeable: true,
33 Data: []irc.Event{ev},
34 Readable: true,
35 }
36+ case *mergedEvent:
37+ var body ui.StyledStringBuilder
38+ if ev.nick != "" && ((ev.firstConnected != 0 && ev.firstConnected == ev.lastConnected) || ev.modeSet != "" || ev.modeUnset != "" || (ev.oldNick != "" && ev.oldNick != ev.nick)) {
39+ if ev.firstConnected != 0 && ev.firstConnected == ev.lastConnected {
40+ if ev.firstConnected == -1 {
41+ body.SetStyle(vaxis.Style{
42+ Foreground: ui.ColorRed,
43+ })
44+ body.WriteByte('-')
45+ } else {
46+ body.SetStyle(vaxis.Style{
47+ Foreground: vaxis.IndexColor(2),
48+ })
49+ body.WriteByte('+')
50+ }
51+ }
52+ if ev.modeSet != "" || ev.modeUnset != "" {
53+ body.SetStyle(vaxis.Style{
54+ Foreground: app.cfg.Colors.Status,
55+ })
56+ body.WriteByte('[')
57+ if ev.modeSet != "" {
58+ body.WriteByte('+')
59+ body.WriteString(ev.modeSet)
60+ }
61+ if ev.modeUnset != "" {
62+ body.WriteByte('-')
63+ body.WriteString(ev.modeSet)
64+ }
65+ body.WriteByte(']')
66+ }
67+ if ev.oldNick != "" && ev.oldNick != ev.nick {
68+ body.SetStyle(vaxis.Style{
69+ Foreground: app.cfg.Colors.Status,
70+ })
71+ body.WriteString(ev.oldNick)
72+ body.SetStyle(vaxis.Style{})
73+ body.WriteString("\u2192")
74+ }
75+ body.SetStyle(vaxis.Style{
76+ Foreground: app.cfg.Colors.Status,
77+ })
78+ body.WriteString(ev.nick)
79+ } else if ev.nick == "" && ev.channelMode != "" {
80+ body.SetStyle(vaxis.Style{
81+ Foreground: app.cfg.Colors.Status,
82+ })
83+ fmt.Fprintf(&body, "[%s]", ev.channelMode)
84+ } else {
85+ return ui.Line{}
86+ }
87+ return ui.Line{
88+ // Only the Body is used for merged events
89+ Body: body.StyledString(),
90+ }
91 default:
92 return ui.Line{}
93 }
94@@ -1672,82 +1735,129 @@ func (app *App) formatMessage(s *irc.Session, ev irc.MessageEvent) (buffer strin
95
96 func (app *App) mergeLine(former *ui.Line, addition ui.Line) {
97 events := append(former.Data.([]irc.Event), addition.Data.([]irc.Event)...)
98- type flow struct {
99- hide bool
100- state int // -1: newly offline; 1: newly online
101+ flows := make([]*mergedEvent, 0, len(events))
102+ flowNick := func(nick string) *mergedEvent {
103+ nickCf := strings.ToLower(nick)
104+ for _, f := range flows {
105+ if strings.ToLower(f.nick) == nickCf {
106+ return f
107+ }
108+ }
109+ return nil
110 }
111- flows := make(map[string]*flow)
112
113- eventFlows := make([]*flow, len(events))
114-
115- for i, ev := range events {
116+ for _, ev := range events {
117 switch ev := ev.(type) {
118 case irc.UserNickEvent:
119- userCf := strings.ToLower(ev.User)
120- f, ok := flows[strings.ToLower(ev.FormerNick)]
121- if ok {
122- flows[userCf] = f
123- delete(flows, strings.ToLower(ev.FormerNick))
124- eventFlows[i] = f
125+ if f := flowNick(ev.User); f != nil {
126+ // Drop any existing flow on the target user, effectively replacing it
127+ // with this new nick. Not very "accurate", but handles disconnects/reconnects/alternate nicks
128+ // quietly enough.
129+ for i, ff := range flows {
130+ if f == ff {
131+ flows = append(flows[:i], flows[i+1:]...)
132+ break
133+ }
134+ }
135+ }
136+ f := flowNick(ev.FormerNick)
137+ if f != nil {
138+ f.nick = ev.User
139 } else {
140- f = &flow{}
141- flows[userCf] = f
142- eventFlows[i] = f
143+ flows = append(flows, &mergedEvent{
144+ oldNick: ev.FormerNick,
145+ nick: ev.User,
146+ })
147 }
148 case irc.UserJoinEvent:
149- userCf := strings.ToLower(ev.User)
150- f, ok := flows[userCf]
151- if ok {
152- if f.state == -1 {
153- f.hide = true
154- delete(flows, userCf)
155+ f := flowNick(ev.User)
156+ if f != nil {
157+ if f.firstConnected == 0 {
158+ f.firstConnected = 1
159 }
160+ f.lastConnected = 1
161+ f.modeSet = ""
162+ f.modeUnset = ""
163 } else {
164- f = &flow{
165- state: 1,
166- }
167- flows[userCf] = f
168- eventFlows[i] = f
169+ flows = append(flows, &mergedEvent{
170+ nick: ev.User,
171+ firstConnected: 1,
172+ lastConnected: 1,
173+ })
174 }
175 case irc.UserPartEvent:
176- userCf := strings.ToLower(ev.User)
177- f, ok := flows[userCf]
178- if ok {
179- if f.state == 1 {
180- f.hide = true
181- delete(flows, userCf)
182+ f := flowNick(ev.User)
183+ if f != nil {
184+ if f.firstConnected == 0 {
185+ f.firstConnected = -1
186 }
187+ f.lastConnected = -1
188+ f.modeSet = ""
189+ f.modeUnset = ""
190 } else {
191- f = &flow{
192- state: -1,
193- }
194- flows[userCf] = f
195- eventFlows[i] = f
196+ flows = append(flows, &mergedEvent{
197+ nick: ev.User,
198+ firstConnected: -1,
199+ lastConnected: -1,
200+ })
201 }
202 case irc.UserQuitEvent:
203- userCf := strings.ToLower(ev.User)
204- f, ok := flows[userCf]
205- if ok {
206- if f.state == 1 {
207- f.hide = true
208- delete(flows, userCf)
209+ f := flowNick(ev.User)
210+ if f != nil {
211+ if f.firstConnected == 0 {
212+ f.firstConnected = -1
213 }
214+ f.lastConnected = -1
215+ f.modeSet = ""
216+ f.modeUnset = ""
217 } else {
218- f = &flow{
219- state: -1,
220- }
221- flows[userCf] = f
222- eventFlows[i] = f
223+ flows = append(flows, &mergedEvent{
224+ nick: ev.User,
225+ firstConnected: -1,
226+ lastConnected: -1,
227+ })
228 }
229 case irc.ModeChangeEvent:
230- userCf := strings.ToLower(strings.Split(ev.Mode, " ")[1])
231- f, ok := flows[userCf]
232- if ok {
233- eventFlows[i] = f
234+ // best-effort heuristic for guessing simple user mode changes:
235+ // expect "<+/-><chars> <args...>" with as many chars as args
236+
237+ mode := strings.Split(ev.Mode, " ")
238+ modeStr := mode[0]
239+ modeArgs := mode[1:]
240+ if len(modeStr) > 0 && (modeStr[0] == '+' || modeStr[0] == '-') && len(modeArgs) == len(modeStr)-1 {
241+ set := modeStr[0] == '+'
242+ for i, nick := range modeArgs {
243+ f := flowNick(nick)
244+ if f == nil {
245+ f = &mergedEvent{
246+ nick: nick,
247+ }
248+ flows = append(flows, f)
249+ }
250+
251+ mode := string(modeStr[i+1])
252+ if set {
253+ if strings.Contains(f.modeUnset, mode) {
254+ f.modeUnset = strings.Replace(f.modeUnset, mode, "", -1)
255+ } else if !strings.Contains(f.modeSet, mode) {
256+ f.modeSet += mode
257+ }
258+ } else {
259+ if strings.Contains(f.modeSet, mode) {
260+ f.modeSet = strings.Replace(f.modeSet, mode, "", -1)
261+ } else if !strings.Contains(f.modeUnset, mode) {
262+ f.modeUnset += mode
263+ }
264+ }
265+ }
266 } else {
267- f = &flow{}
268- flows[userCf] = f
269- eventFlows[i] = f
270+ if f := flowNick(""); f != nil && f.channelMode == ev.Mode {
271+ // setting the same channel mode string, ignore
272+ } else {
273+ flows = append(flows, &mergedEvent{
274+ channelMode: ev.Mode,
275+ })
276+ }
277 }
278 }
279 }
280@@ -1755,11 +1865,11 @@ func (app *App) mergeLine(former *ui.Line, addition ui.Line) {
281 newBody := new(ui.StyledStringBuilder)
282 newBody.Grow(128)
283 first := true
284- for i, ev := range events {
285- if f := eventFlows[i]; f == nil || f.hide {
286+ for _, f := range flows {
287+ l := app.formatEvent(f)
288+ if l.IsZero() {
289 continue
290 }
291- l := app.formatEvent(ev)
292 if first {
293 first = false
294 } else {