~taiite/public-inbox

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
1

[PATCH senpai v2] Add support for draft/event-playback

Details
Message ID
<20211124155900.23163-1-delthas@dille.cc>
DKIM signature
pass
Download raw message
Patch: +229 -104
- Refactor formatting lines into a function
- Store event times in the event
- Refactor merging lines into a function
- Always merge lines from the history, now that we will add mergeable
  lines with CHATHISTORY

Successfully tested locally.
---
 app.go         | 186 +++++++++++++++++++++++++++++--------------------
 irc/events.go  |   6 ++
 irc/session.go |  88 +++++++++++++++++++++--
 ui/buffers.go  |  53 ++++++++------
 4 files changed, 229 insertions(+), 104 deletions(-)

diff --git a/app.go b/app.go
index 7b416c8..00c31d2 100644
--- a/app.go
+++ b/app.go
@@ -642,21 +642,9 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
			Highlight: true,
		})
	case irc.UserNickEvent:
		var body ui.StyledStringBuilder
		body.WriteString(fmt.Sprintf("%s\u2192%s", ev.FormerNick, ev.User))
		textStyle := tcell.StyleDefault.Foreground(tcell.ColorGray)
		arrowStyle := tcell.StyleDefault
		body.AddStyle(0, textStyle)
		body.AddStyle(len(ev.FormerNick), arrowStyle)
		body.AddStyle(body.Len()-len(ev.User), textStyle)
		line := app.formatEvent(ev)
		for _, c := range s.ChannelsSharedWith(ev.User) {
			app.win.AddLine(netID, c, ui.NotifyNone, ui.Line{
				At:        msg.TimeOrNow(),
				Head:      "--",
				HeadColor: tcell.ColorGray,
				Body:      body.StyledString(),
				Mergeable: true,
			})
			app.win.AddLine(netID, c, ui.NotifyNone, line)
		}
	case irc.SelfJoinEvent:
		i, added := app.win.AddBuffer(netID, "", ev.Channel)
@@ -685,70 +673,27 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
			app.lastBuffer = ""
		}
	case irc.UserJoinEvent:
		var body ui.StyledStringBuilder
		body.Grow(len(ev.User) + 1)
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGreen))
		body.WriteByte('+')
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGray))
		body.WriteString(ev.User)
		app.win.AddLine(netID, ev.Channel, ui.NotifyNone, ui.Line{
			At:        msg.TimeOrNow(),
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      body.StyledString(),
			Mergeable: true,
		})
		line := app.formatEvent(ev)
		app.win.AddLine(netID, ev.Channel, ui.NotifyNone, line)
	case irc.SelfPartEvent:
		app.win.RemoveBuffer(netID, ev.Channel)
		delete(app.messageBounds, boundKey{netID, ev.Channel})
	case irc.UserPartEvent:
		var body ui.StyledStringBuilder
		body.Grow(len(ev.User) + 1)
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorRed))
		body.WriteByte('-')
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGray))
		body.WriteString(ev.User)
		app.win.AddLine(netID, ev.Channel, ui.NotifyNone, ui.Line{
			At:        msg.TimeOrNow(),
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      body.StyledString(),
			Mergeable: true,
		})
		line := app.formatEvent(ev)
		app.win.AddLine(netID, ev.Channel, ui.NotifyNone, line)
	case irc.UserQuitEvent:
		var body ui.StyledStringBuilder
		body.Grow(len(ev.User) + 1)
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorRed))
		body.WriteByte('-')
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGray))
		body.WriteString(ev.User)
		line := app.formatEvent(ev)
		for _, c := range ev.Channels {
			app.win.AddLine(netID, c, ui.NotifyNone, ui.Line{
				At:        msg.TimeOrNow(),
				Head:      "--",
				HeadColor: tcell.ColorGray,
				Body:      body.StyledString(),
				Mergeable: true,
			})
			app.win.AddLine(netID, c, ui.NotifyNone, line)
		}
	case irc.TopicChangeEvent:
		line := app.formatEvent(ev)
		app.win.AddLine(netID, ev.Channel, ui.NotifyUnread, line)
		topic := ui.IRCString(ev.Topic).String()
		body := fmt.Sprintf("Topic changed to: %s", topic)
		app.win.SetTopic(netID, ev.Channel, topic)
		app.win.AddLine(netID, ev.Channel, ui.NotifyUnread, ui.Line{
			At:        msg.TimeOrNow(),
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      ui.Styled(body, tcell.StyleDefault.Foreground(tcell.ColorGray)),
		})
	case irc.ModeChangeEvent:
		body := fmt.Sprintf("Mode change: %s", ev.Mode)
		app.win.AddLine(netID, ev.Channel, ui.NotifyUnread, ui.Line{
			At:        msg.TimeOrNow(),
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      ui.Styled(body, tcell.StyleDefault.Foreground(tcell.ColorGray)),
		})
		line := app.formatEvent(ev)
		app.win.AddLine(netID, ev.Channel, ui.NotifyUnread, line)
	case irc.InviteEvent:
		var buffer string
		var notify ui.NotifyType
@@ -807,19 +752,25 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
		var linesAfter []ui.Line
		bounds, hasBounds := app.messageBounds[boundKey{netID, ev.Target}]
		for _, m := range ev.Messages {
			var line ui.Line
			switch ev := m.(type) {
			case irc.MessageEvent:
				_, line, _ := app.formatMessage(s, ev)
				if hasBounds {
					c := bounds.Compare(&line)
					if c < 0 {
						linesBefore = append(linesBefore, line)
					} else if c > 0 {
						linesAfter = append(linesAfter, line)
					}
				} else {
				_, line, _ = app.formatMessage(s, ev)
			default:
				line = app.formatEvent(ev)
			}
			if line.IsZero() {
				continue
			}
			if hasBounds {
				c := bounds.Compare(&line)
				if c < 0 {
					linesBefore = append(linesBefore, line)
				} else if c > 0 {
					linesAfter = append(linesAfter, line)
				}
			} else {
				linesBefore = append(linesBefore, line)
			}
		}
		app.win.AddLines(netID, ev.Target, linesBefore, linesAfter)
@@ -987,6 +938,89 @@ func (app *App) completions(cursorIdx int, text []rune) []ui.Completion {
	return cs
}

// formatEvent returns a formatted ui.Line for an irc.Event.
func (app *App) formatEvent(ev irc.Event) ui.Line {
	switch ev := ev.(type) {
	case irc.UserNickEvent:
		var body ui.StyledStringBuilder
		body.WriteString(fmt.Sprintf("%s\u2192%s", ev.FormerNick, ev.User))
		textStyle := tcell.StyleDefault.Foreground(tcell.ColorGray)
		arrowStyle := tcell.StyleDefault
		body.AddStyle(0, textStyle)
		body.AddStyle(len(ev.FormerNick), arrowStyle)
		body.AddStyle(body.Len()-len(ev.User), textStyle)

		return ui.Line{
			At:        ev.Time,
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      body.StyledString(),
			Mergeable: true,
		}
	case irc.UserJoinEvent:
		var body ui.StyledStringBuilder
		body.Grow(len(ev.User) + 1)
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGreen))
		body.WriteByte('+')
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGray))
		body.WriteString(ev.User)
		return ui.Line{
			At:        ev.Time,
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      body.StyledString(),
			Mergeable: true,
		}
	case irc.UserPartEvent:
		var body ui.StyledStringBuilder
		body.Grow(len(ev.User) + 1)
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorRed))
		body.WriteByte('-')
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGray))
		body.WriteString(ev.User)
		return ui.Line{
			At:        ev.Time,
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      body.StyledString(),
			Mergeable: true,
		}
	case irc.UserQuitEvent:
		var body ui.StyledStringBuilder
		body.Grow(len(ev.User) + 1)
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorRed))
		body.WriteByte('-')
		body.SetStyle(tcell.StyleDefault.Foreground(tcell.ColorGray))
		body.WriteString(ev.User)
		return ui.Line{
			At:        ev.Time,
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      body.StyledString(),
			Mergeable: true,
		}
	case irc.TopicChangeEvent:
		topic := ui.IRCString(ev.Topic).String()
		body := fmt.Sprintf("Topic changed to: %s", topic)
		return ui.Line{
			At:        ev.Time,
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      ui.Styled(body, tcell.StyleDefault.Foreground(tcell.ColorGray)),
		}
	case irc.ModeChangeEvent:
		body := fmt.Sprintf("Mode change: %s", ev.Mode)
		return ui.Line{
			At:        ev.Time,
			Head:      "--",
			HeadColor: tcell.ColorGray,
			Body:      ui.Styled(body, tcell.StyleDefault.Foreground(tcell.ColorGray)),
		}
	default:
		return ui.Line{}
	}
}

// formatMessage sets how a given message must be formatted.
//
// It computes three things:
diff --git a/irc/events.go b/irc/events.go
index 70a6c1d..0c84f2b 100644
--- a/irc/events.go
+++ b/irc/events.go
@@ -19,6 +19,7 @@ type SelfNickEvent struct {
type UserNickEvent struct {
	User       string
	FormerNick string
	Time       time.Time
}

type SelfJoinEvent struct {
@@ -30,6 +31,7 @@ type SelfJoinEvent struct {
type UserJoinEvent struct {
	User    string
	Channel string
	Time    time.Time
}

type SelfPartEvent struct {
@@ -39,21 +41,25 @@ type SelfPartEvent struct {
type UserPartEvent struct {
	User    string
	Channel string
	Time    time.Time
}

type UserQuitEvent struct {
	User     string
	Channels []string
	Time     time.Time
}

type TopicChangeEvent struct {
	Channel string
	Topic   string
	Time    time.Time
}

type ModeChangeEvent struct {
	Channel string
	Mode    string
	Time    time.Time
}

type InviteEvent struct {
diff --git a/irc/session.go b/irc/session.go
index 8467f2c..bbf01f6 100644
--- a/irc/session.go
+++ b/irc/session.go
@@ -58,6 +58,7 @@ var SupportedCapabilities = map[string]struct{}{
	"setname":       {},

	"draft/chathistory":        {},
	"draft/event-playback":     {},
	"soju.im/bouncer-networks": {},
}

@@ -527,18 +528,23 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			}
			s.targetsBatch.Targets[target] = t
		} else if b, ok := s.chBatches[id]; ok {
			ev, err := s.newMessageEvent(msg)
			ev, err := s.handleMessageRegistered(msg, true)
			if err != nil {
				return nil, err
			}
			s.chBatches[id] = HistoryEvent{
				Target:   b.Target,
				Messages: append(b.Messages, ev),
			if ev != nil {
				s.chBatches[id] = HistoryEvent{
					Target:   b.Target,
					Messages: append(b.Messages, ev),
				}
				return nil, nil
			}
			return nil, nil
		}
	}
	return s.handleMessageRegistered(msg, false)
}

func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, error) {
	switch msg.Command {
	case "AUTHENTICATE":
		if s.auth == nil {
@@ -663,6 +669,14 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return nil, err
		}

		if playback {
			return UserJoinEvent{
				User:    msg.Prefix.Name,
				Channel: channel,
				Time:    msg.TimeOrNow(),
			}, nil
		}

		nickCf := s.Casemap(msg.Prefix.Name)
		channelCf := s.Casemap(channel)

@@ -685,6 +699,7 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return UserJoinEvent{
				User:    msg.Prefix.Name,
				Channel: c.Name,
				Time:    msg.TimeOrNow(),
			}, nil
		}
	case "PART":
@@ -697,6 +712,14 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return nil, err
		}

		if playback {
			return UserPartEvent{
				User:    msg.Prefix.Name,
				Channel: channel,
				Time:    msg.TimeOrNow(),
			}, nil
		}

		nickCf := s.Casemap(msg.Prefix.Name)
		channelCf := s.Casemap(channel)

@@ -718,6 +741,7 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
				return UserPartEvent{
					User:    u.Name.Name,
					Channel: c.Name,
					Time:    msg.TimeOrNow(),
				}, nil
			}
		}
@@ -727,6 +751,14 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return nil, err
		}

		if playback {
			return UserPartEvent{
				User:    nick,
				Channel: channel,
				Time:    msg.TimeOrNow(),
			}, nil
		}

		nickCf := s.Casemap(nick)
		channelCf := s.Casemap(channel)

@@ -748,6 +780,7 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
				return UserPartEvent{
					User:    nick,
					Channel: c.Name,
					Time:    msg.TimeOrNow(),
				}, nil
			}
		}
@@ -756,6 +789,13 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return nil, errMissingPrefix
		}

		if playback {
			return UserQuitEvent{
				User: msg.Prefix.Name,
				Time: msg.TimeOrNow(),
			}, nil
		}

		nickCf := s.Casemap(msg.Prefix.Name)

		if u, ok := s.users[nickCf]; ok {
@@ -771,6 +811,7 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return UserQuitEvent{
				User:     u.Name.Name,
				Channels: channels,
				Time:     msg.TimeOrNow(),
			}, nil
		}
	case rplNamreply:
@@ -864,6 +905,14 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return nil, err
		}

		if playback {
			return TopicChangeEvent{
				Channel: channel,
				Topic:   topic,
				Time:    msg.TimeOrNow(),
			}, nil
		}

		channelCf := s.Casemap(channel)

		if c, ok := s.channels[channelCf]; ok {
@@ -874,6 +923,7 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return TopicChangeEvent{
				Channel: c.Name,
				Topic:   c.Topic,
				Time:    msg.TimeOrNow(),
			}, nil
		}
	case "MODE":
@@ -882,6 +932,14 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return nil, err
		}

		if playback {
			return ModeChangeEvent{
				Channel: channel,
				Mode:    mode,
				Time:    msg.TimeOrNow(),
			}, nil
		}

		channelCf := s.Casemap(channel)

		if c, ok := s.channels[channelCf]; ok {
@@ -917,7 +975,8 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			s.channels[channelCf] = c
			return ModeChangeEvent{
				Channel: c.Name,
				Mode:    strings.Join(msg.Params[1:], " "),
				Mode:    mode,
				Time:    msg.TimeOrNow(),
			}, nil
		}
	case "INVITE":
@@ -966,12 +1025,20 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return nil, err
		}

		if playback {
			return s.newMessageEvent(msg)
		}

		targetCf := s.casemap(target)
		nickCf := s.casemap(msg.Prefix.Name)
		s.typings.Done(targetCf, nickCf)

		return s.newMessageEvent(msg)
	case "TAGMSG":
		if playback {
			return nil, nil
		}

		if msg.Prefix == nil {
			return nil, errMissingPrefix
		}
@@ -1048,6 +1115,14 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return nil, err
		}

		if playback {
			return UserNickEvent{
				User:       nick,
				FormerNick: msg.Prefix.Name,
				Time:       msg.TimeOrNow(),
			}, nil
		}

		nickCf := s.Casemap(msg.Prefix.Name)
		newNick := nick
		newNickCf := s.Casemap(newNick)
@@ -1070,6 +1145,7 @@ func (s *Session) handleRegistered(msg Message) (Event, error) {
			return UserNickEvent{
				User:       nick,
				FormerNick: msg.Prefix.Name,
				Time:       msg.TimeOrNow(),
			}, nil
		}
	case "BOUNCER":
diff --git a/ui/buffers.go b/ui/buffers.go
index 9a20abe..d277491 100644
--- a/ui/buffers.go
+++ b/ui/buffers.go
@@ -39,6 +39,21 @@ type Line struct {
	newLines    []int
}

func (l *Line) IsZero() bool {
	return l.Body.string == ""
}

func (l *Line) Merge(line Line) {
	newBody := new(StyledStringBuilder)
	newBody.Grow(len(l.Body.string) + 2 + len(line.Body.string))
	newBody.WriteStyledString(l.Body)
	newBody.WriteString("  ")
	newBody.WriteStyledString(line.Body)
	l.Body = newBody.StyledString()
	l.computeSplitPoints()
	l.width = 0
}

func (l *Line) computeSplitPoints() {
	if l.splitPoints == nil {
		l.splitPoints = []point{}
@@ -307,14 +322,7 @@ func (bs *BufferList) AddLine(netID, title string, notify NotifyType, line Line)

	if line.Mergeable && n != 0 && b.lines[n-1].Mergeable {
		l := &b.lines[n-1]
		newBody := new(StyledStringBuilder)
		newBody.Grow(len(l.Body.string) + 2 + len(line.Body.string))
		newBody.WriteStyledString(l.Body)
		newBody.WriteString("  ")
		newBody.WriteStyledString(line.Body)
		l.Body = newBody.StyledString()
		l.computeSplitPoints()
		l.width = 0
		l.Merge(line)
		// TODO change b.scrollAmt if it's not 0 and bs.current is idx.
	} else {
		line.computeSplitPoints()
@@ -340,21 +348,22 @@ func (bs *BufferList) AddLines(netID, title string, before, after []Line) {

	b := &bs.list[idx]

	for i := 0; i < len(before); i++ {
		before[i].Body = before[i].Body.ParseURLs()
		before[i].computeSplitPoints()
	}
	for i := 0; i < len(after); i++ {
		after[i].Body = after[i].Body.ParseURLs()
		after[i].computeSplitPoints()
	}

	if len(before) != 0 {
		b.lines = append(before, b.lines...)
	}
	if len(after) != 0 {
		b.lines = append(b.lines, after...)
	lines := make([]Line, 0, len(before)+len(b.lines)+len(after))
	for _, buf := range []*[]Line{&before, &b.lines, &after} {
		for _, line := range *buf {
			if line.Mergeable && len(lines) > 0 && lines[len(lines)-1].Mergeable {
				l := &lines[len(lines)-1]
				l.Merge(line)
			} else {
				if buf != &b.lines {
					line.Body = line.Body.ParseURLs()
					line.computeSplitPoints()
				}
				lines = append(lines, line)
			}
		}
	}
	b.lines = lines
}

func (bs *BufferList) SetTopic(netID, title string, topic string) {

base-commit: d41d7c5d975a4b9d80779065c4a4d9e09b35ae91
-- 
2.17.1
Details
Message ID
<a8246a91-275d-90c4-4635-852dcabcc32a@hirtz.pm>
In-Reply-To
<20211124155900.23163-1-delthas@dille.cc> (view parent)
DKIM signature
pass
Download raw message
Pushed.
Reply to thread Export thread (mbox)