commit 466484d

sewn  ·  2026-07-28 11:20:26 +0000 UTC
parent ea46a8d
Add displaying message replyto
5 files changed,  +119, -12
M app.go
M app.go
+2, -0
1@@ -2145,6 +2145,8 @@ func (app *App) formatMessage(s *irc.Session, ev irc.MessageEvent) (buffer strin
2 
3 	line = ui.Line{
4 		At:        ev.Time,
5+		ID:        ev.ID,
6+		ReplyTo:   ev.ReplyTo,
7 		Head:      head.StyledString(),
8 		Notify:    notification,
9 		Body:      body.StyledString(),
+2, -0
1@@ -88,6 +88,8 @@ type InviteEvent struct {
2 }
3 
4 type MessageEvent struct {
5+	ID              string
6+	ReplyTo         string
7 	User            string
8 	Target          string
9 	TargetIsChannel bool
+2, -0
1@@ -2182,6 +2182,8 @@ func (s *Session) newMessageEvent(msg Message) (ev MessageEvent, err error) {
2 	target := rawTarget[i:]
3 
4 	ev = MessageEvent{
5+		ID:           msg.ID(),
6+		ReplyTo:      msg.ReplyTo(),
7 		User:         msg.Prefix.Name, // TODO correctly casemap
8 		Target:       target,          // TODO correctly casemap
9 		TargetPrefix: prefix,
+12, -0
 1@@ -377,6 +377,18 @@ func parseTimestamp(timestamp string) (time.Time, bool) {
 2 	return t.UTC(), true
 3 }
 4 
 5+func (msg *Message) ID() string {
 6+	return msg.Tags["msgid"]
 7+}
 8+
 9+// ReplyTo returns the msgid of the message this one is a reply to, if any.
10+func (msg *Message) ReplyTo() string {
11+	if id, ok := msg.Tags["+draft/reply"]; ok {
12+		return id
13+	}
14+	return msg.Tags["+reply"]
15+}
16+
17 // Time returns the time when the message has been sent, if present.
18 func (msg *Message) Time() (t time.Time, ok bool) {
19 	tag, ok := msg.Tags["time"]
+101, -12
  1@@ -50,11 +50,20 @@ type Line struct {
  2 	Mergeable bool
  3 	Data      interface{}
  4 
  5+	ID      string
  6+	ReplyTo string
  7+	Reply   *Reply
  8+
  9 	splitPoints []point
 10 	width       int
 11 	newLines    []int
 12 }
 13 
 14+type Reply struct {
 15+	Nick StyledString
 16+	Body StyledString
 17+}
 18+
 19 func (l *Line) IsZero() bool {
 20 	return l.Body.string == ""
 21 }
 22@@ -194,6 +203,16 @@ func (l *Line) NewLines(vx *Vaxis, width int) []int {
 23 	return l.newLines
 24 }
 25 
 26+// Rows returns the number of terminal rows l occupies at the given width,
 27+// including its reply preview row, if any.
 28+func (l *Line) Rows(vx *Vaxis, width int) int {
 29+	n := len(l.NewLines(vx, width)) + 1
 30+	if l.Reply != nil {
 31+		n++
 32+	}
 33+	return n
 34+}
 35+
 36 type buffer struct {
 37 	netID         string
 38 	netName       string
 39@@ -501,6 +520,10 @@ func (bs *BufferList) AddLine(netID, title string, line Line) {
 40 	}
 41 	current := bs.cur()
 42 
 43+	if line.ReplyTo != "" {
 44+		line.Reply = bs.resolveReply(netID, title, line.ReplyTo)
 45+	}
 46+
 47 	n := len(b.lines)
 48 	line.At = line.At.UTC()
 49 
 50@@ -518,7 +541,7 @@ func (bs *BufferList) AddLine(netID, title string, line Line) {
 51 		line.computeSplitPoints(bs.ui.vx)
 52 		b.lines = append(b.lines, line)
 53 		if b == current && 0 < b.scrollAmt {
 54-			b.scrollAmt += len(line.NewLines(bs.ui.vx, bs.textWidth)) + 1
 55+			b.scrollAmt += line.Rows(bs.ui.vx, bs.textWidth)
 56 		}
 57 	}
 58 
 59@@ -537,6 +560,46 @@ func (bs *BufferList) AddLine(netID, title string, line Line) {
 60 	}
 61 }
 62 
 63+func (bs *BufferList) resolveReply(netID, title, replyTo string) *Reply {
 64+	_, b := bs.at(netID, title)
 65+	if b == nil {
 66+		return nil
 67+	}
 68+	for i := len(b.lines) - 1; i >= 0; i-- {
 69+		if b.lines[i].ID == replyTo {
 70+			return &Reply{
 71+				Nick: b.lines[i].Head,
 72+				Body: b.lines[i].Body,
 73+			}
 74+		}
 75+	}
 76+	return nil
 77+}
 78+
 79+func (bs *BufferList) replyPreviewText(reply *Reply) StyledString {
 80+	var sb StyledStringBuilder
 81+	sb.WriteStyledString(ColorString("╭─ ", bs.ui.config.Colors.Gray))
 82+
 83+	if reply == nil {
 84+		sb.WriteStyledString(ColorString("(message not found)", bs.ui.config.Colors.Gray))
 85+		return sb.StyledString()
 86+	}
 87+
 88+	sb.WriteStyledString(reply.Nick)
 89+	sb.WriteStyledString(ColorString(": ", bs.ui.config.Colors.Gray))
 90+
 91+	snippet := strings.Map(func(r rune) rune {
 92+		if r == '\n' || r == '\r' {
 93+			return ' '
 94+		}
 95+		return r
 96+	}, reply.Body.string)
 97+	snippet = truncate(bs.ui.vx, snippet, max(bs.textWidth, 48), "…")
 98+	sb.WriteStyledString(ColorString(snippet, bs.ui.config.Colors.Gray))
 99+
100+	return sb.StyledString()
101+}
102+
103 func (bs *BufferList) AddLines(netID, title string, before, after []Line) {
104 	_, b := bs.at(netID, title)
105 	if b == nil {
106@@ -559,6 +622,17 @@ func (bs *BufferList) AddLines(netID, title string, before, after []Line) {
107 					}
108 					line.computeSplitPoints(bs.ui.vx)
109 				}
110+				if line.ReplyTo != "" && line.Reply == nil {
111+					for j := len(lines) - 1; j >= 0; j-- {
112+						if lines[j].ID == line.ReplyTo {
113+							line.Reply = &Reply{Nick: lines[j].Head, Body: lines[j].Body}
114+							break
115+						}
116+					}
117+					if line.Reply == nil {
118+						line.Reply = bs.resolveReply(netID, title, line.ReplyTo)
119+					}
120+				}
121 				lines = append(lines, line)
122 			}
123 
124@@ -816,7 +890,7 @@ func (bs *BufferList) forEachLine(b *buffer, f func(line *Line, y int) bool) boo
125 		if f(line, y) {
126 			return true
127 		}
128-		y += len(line.NewLines(bs.ui.vx, bs.textWidth)) + 1
129+		y += line.Rows(bs.ui.vx, bs.textWidth)
130 	}
131 	return false
132 }
133@@ -1215,37 +1289,52 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
134 		}
135 
136 		yi -= len(nls) + 1
137+		if line.Reply != nil {
138+			yi--
139+		}
140 		if y0+bs.tlHeight <= yi {
141 			continue
142 		}
143 
144-		showDate := bs.shouldShowDate(b, i, yi, y0)
145+		cY := yi
146+		if line.Reply != nil {
147+			cY++
148+			if yi >= y0 {
149+				preview := bs.replyPreviewText(line.Reply)
150+				preview.string = truncate(bs.ui.vx, preview.string,
151+					nickColWidth+2+bs.textWidth, "…")
152+				x := x0 + nickColWidth + 6
153+				printString(vx, &x, yi, preview)
154+			}
155+		}
156+
157+		showDate := bs.shouldShowDate(b, i, cY, y0)
158 		if showDate {
159 			st := vaxis.Style{
160 				Attribute: vaxis.AttrBold,
161 			}
162 			// as a special case, always draw the first visible message date, even if it is a continuation line
163-			yd := yi
164+			yd := cY
165 			if yd < y0 {
166 				yd = y0
167 			}
168 			printDate(vx, x0, yd, st, line.At.Local())
169 		} else {
170-			showTime := b.lines[i-1].At.Truncate(time.Minute) != line.At.Truncate(time.Minute) && yi >= y0
171+			showTime := b.lines[i-1].At.Truncate(time.Minute) != line.At.Truncate(time.Minute) && cY >= y0
172 			if !showTime {
173 				// also try to show the time if we previously drew the date
174-				yp := yi - (len(b.lines[i-1].NewLines(bs.ui.vx, bs.textWidth)) + 1)
175+				yp := cY - b.lines[i-1].Rows(bs.ui.vx, bs.textWidth)
176 				showTime = i == 0 || bs.shouldShowDate(b, i-1, yp, y0)
177 			}
178 			if showTime {
179 				st := vaxis.Style{
180 					Foreground: bs.ui.config.Colors.Gray,
181 				}
182-				printTime(vx, x0, yi, st, line.At.Local())
183+				printTime(vx, x0, cY, st, line.At.Local())
184 			}
185 		}
186 
187-		if yi >= y0 {
188+		if cY >= y0 {
189 			head := line.Head
190 			if line.Highlight && len(line.Head.styles) > 0 {
191 				var sb StyledStringBuilder
192@@ -1257,7 +1346,7 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
193 				}
194 				head = sb.StyledString()
195 			}
196-			xb, xe := printIdent(vx, x0+7, yi, nickColWidth, head)
197+			xb, xe := printIdent(vx, x0+7, cY, nickColWidth, head)
198 
199 			lastHead := line.Head.string
200 			if len(line.Head.styles) > 0 {
201@@ -1268,7 +1357,7 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
202 				ui.clickEvents = append(ui.clickEvents, clickEvent{
203 					xb: xb,
204 					xe: xe,
205-					y:  yi,
206+					y:  cY,
207 					event: &events.EventClickNick{
208 						EventClick: events.EventClick{
209 							NetID:  b.netID,
210@@ -1281,7 +1370,7 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
211 		}
212 
213 		x := x1
214-		y := yi
215+		y := cY
216 		var style vaxis.Style
217 		nextStyles := line.Body.styles
218 
219@@ -1305,7 +1394,7 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
220 				}
221 			}
222 
223-			if y != yi && x == x1 && IsSplitRune(l[0]) {
224+			if y != cY && x == x1 && IsSplitRune(l[0]) {
225 				lbi += len(string(l[0]))
226 				l = l[1:]
227 				continue