diff options
-rw-r--r-- | app.go | 186 | ||||
-rw-r--r-- | irc/events.go | 6 | ||||
-rw-r--r-- | irc/session.go | 88 | ||||
-rw-r--r-- | ui/buffers.go | 53 |
4 files changed, 229 insertions, 104 deletions
@@ -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) { |