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 {