From 3750d5007fe31b1a4d706357f43774d08944213e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 18 Apr 2018 13:38:33 +0300 Subject: Create pills when tab-completing or clicking nicks --- ui/message-view.go | 28 ++++++++++++++++++---------- ui/messages/base.go | 11 +++++++++-- ui/messages/expandedtextmessage.go | 4 ++-- ui/messages/imagemessage.go | 4 ++-- ui/messages/message.go | 1 + ui/messages/parser/parser.go | 29 +++++++++++++++++------------ ui/messages/textbase.go | 4 ++-- ui/messages/textmessage.go | 4 ++-- ui/room-view.go | 17 +++++++++-------- ui/view-main.go | 11 ++++++++--- 10 files changed, 70 insertions(+), 43 deletions(-) diff --git a/ui/message-view.go b/ui/message-view.go index 83729ca..b78f135 100644 --- a/ui/message-view.go +++ b/ui/message-view.go @@ -21,7 +21,9 @@ import ( "fmt" "math" "os" + "strings" + "github.com/mattn/go-runewidth" "maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/lib/open" @@ -278,22 +280,28 @@ func (view *MessageView) handleUsernameClick(message ifc.MessageMeta, prevMessag return false } - sender := []rune(uiMessage.Sender()) - if len(sender) == 0 { + if len(uiMessage.Sender()) == 0 { return false } + sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", uiMessage.Sender(), uiMessage.SenderID()) cursorPos := view.parent.input.GetCursorOffset() - text := []rune(view.parent.input.GetText()) - var newText []rune + text := view.parent.input.GetText() + var buf strings.Builder if cursorPos == 0 { - newText = append(sender, ':', ' ') - newText = append(newText, text...) + buf.WriteString(sender) + buf.WriteRune(':') + buf.WriteRune(' ') + buf.WriteString(text) } else { - newText = append(text[0:cursorPos], sender...) - newText = append(newText, ' ') - newText = append(newText, text[cursorPos:]...) - } + textBefore := runewidth.Truncate(text, cursorPos, "") + textAfter := text[len(textBefore):] + buf.WriteString(textBefore) + buf.WriteString(sender) + buf.WriteRune(' ') + buf.WriteString(textAfter) + } + newText := buf.String() view.parent.input.SetText(string(newText)) view.parent.input.SetCursorOffset(cursorPos + len(newText) - len(text)) return true diff --git a/ui/messages/base.go b/ui/messages/base.go index aed7903..50f2735 100644 --- a/ui/messages/base.go +++ b/ui/messages/base.go @@ -33,6 +33,7 @@ func init() { type BaseMessage struct { MsgID string MsgType string + MsgSenderID string MsgSender string MsgSenderColor tcell.Color MsgTimestamp time.Time @@ -43,9 +44,10 @@ type BaseMessage struct { prevBufferWidth int } -func newBaseMessage(id, sender, msgtype string, timestamp time.Time) BaseMessage { +func newBaseMessage(id, sender, displayname, msgtype string, timestamp time.Time) BaseMessage { return BaseMessage{ - MsgSender: sender, + MsgSenderID: sender, + MsgSender: displayname, MsgTimestamp: timestamp, MsgSenderColor: widget.GetHashColor(sender), MsgType: msgtype, @@ -66,6 +68,7 @@ func (msg *BaseMessage) CopyFrom(from ifc.MessageMeta) { fromMsg, ok := from.(UIMessage) if ok { + msg.MsgSenderID = fromMsg.SenderID() msg.MsgSender = fromMsg.RealSender() msg.MsgID = fromMsg.ID() msg.MsgType = fromMsg.Type() @@ -99,6 +102,10 @@ func (msg *BaseMessage) Sender() string { } } +func (msg *BaseMessage) SenderID() string { + return msg.MsgSenderID +} + func (msg *BaseMessage) RealSender() string { return msg.MsgSender } diff --git a/ui/messages/expandedtextmessage.go b/ui/messages/expandedtextmessage.go index 3ee15ad..ec359c9 100644 --- a/ui/messages/expandedtextmessage.go +++ b/ui/messages/expandedtextmessage.go @@ -34,9 +34,9 @@ type ExpandedTextMessage struct { } // NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state. -func NewExpandedTextMessage(id, sender, msgtype string, text tstring.TString, timestamp time.Time) UIMessage { +func NewExpandedTextMessage(id, sender, displayname, msgtype string, text tstring.TString, timestamp time.Time) UIMessage { return &ExpandedTextMessage{ - BaseTextMessage: newBaseTextMessage(id, sender, msgtype, timestamp), + BaseTextMessage: newBaseTextMessage(id, sender, displayname, msgtype, timestamp), MsgText: text, } } diff --git a/ui/messages/imagemessage.go b/ui/messages/imagemessage.go index 2fbf6ae..7478876 100644 --- a/ui/messages/imagemessage.go +++ b/ui/messages/imagemessage.go @@ -45,9 +45,9 @@ type ImageMessage struct { } // NewImageMessage creates a new ImageMessage object with the provided values and the default state. -func NewImageMessage(gmx ifc.Gomuks, id, sender, msgtype, homeserver, fileID string, data []byte, timestamp time.Time) UIMessage { +func NewImageMessage(gmx ifc.Gomuks, id, sender, displayname, msgtype, homeserver, fileID string, data []byte, timestamp time.Time) UIMessage { return &ImageMessage{ - newBaseMessage(id, sender, msgtype, timestamp), + newBaseMessage(id, sender, displayname, msgtype, timestamp), homeserver, fileID, data, diff --git a/ui/messages/message.go b/ui/messages/message.go index 6ebfb6d..0d5c9e4 100644 --- a/ui/messages/message.go +++ b/ui/messages/message.go @@ -30,6 +30,7 @@ type UIMessage interface { Buffer() []tstring.TString Height() int + SenderID() string RealSender() string RegisterGomuks(gmx ifc.Gomuks) } diff --git a/ui/messages/parser/parser.go b/ui/messages/parser/parser.go index 939dd10..58b0248 100644 --- a/ui/messages/parser/parser.go +++ b/ui/messages/parser/parser.go @@ -31,15 +31,11 @@ import ( ) func ParseEvent(gmx ifc.Gomuks, room *rooms.Room, evt *gomatrix.Event) messages.UIMessage { - member := room.GetMember(evt.Sender) - if member != nil { - evt.Sender = member.DisplayName - } switch evt.Type { case "m.room.message": return ParseMessage(gmx, room, evt) case "m.room.member": - return ParseMembershipEvent(evt) + return ParseMembershipEvent(room, evt) } return nil } @@ -53,6 +49,11 @@ func unixToTime(unix int64) time.Time { } func ParseMessage(gmx ifc.Gomuks, room *rooms.Room, evt *gomatrix.Event) messages.UIMessage { + displayname := evt.Sender + member := room.GetMember(evt.Sender) + if member != nil { + displayname = member.DisplayName + } msgtype, _ := evt.Content["msgtype"].(string) ts := unixToTime(evt.Timestamp) switch msgtype { @@ -60,10 +61,10 @@ func ParseMessage(gmx ifc.Gomuks, room *rooms.Room, evt *gomatrix.Event) message format, hasFormat := evt.Content["format"].(string) if hasFormat && format == "org.matrix.custom.html" { text := ParseHTMLMessage(room, evt) - return messages.NewExpandedTextMessage(evt.ID, evt.Sender, msgtype, text, ts) + return messages.NewExpandedTextMessage(evt.ID, evt.Sender, displayname, msgtype, text, ts) } else { text, _ := evt.Content["body"].(string) - return messages.NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts) + return messages.NewTextMessage(evt.ID, evt.Sender, displayname, msgtype, text, ts) } case "m.image": url, _ := evt.Content["url"].(string) @@ -71,12 +72,16 @@ func ParseMessage(gmx ifc.Gomuks, room *rooms.Room, evt *gomatrix.Event) message if err != nil { debug.Printf("Failed to download %s: %v", url, err) } - return messages.NewImageMessage(gmx, evt.ID, evt.Sender, msgtype, hs, id, data, ts) + return messages.NewImageMessage(gmx, evt.ID, evt.Sender, displayname, msgtype, hs, id, data, ts) } return nil } -func getMembershipEventContent(evt *gomatrix.Event) (sender string, text tstring.TString) { +func getMembershipEventContent(room *rooms.Room, evt *gomatrix.Event) (sender string, text tstring.TString) { + member := room.GetMember(evt.Sender) + if member != nil { + evt.Sender = member.DisplayName + } membership, _ := evt.Content["membership"].(string) displayname, _ := evt.Content["displayname"].(string) if len(displayname) == 0 { @@ -121,8 +126,8 @@ func getMembershipEventContent(evt *gomatrix.Event) (sender string, text tstring return } -func ParseMembershipEvent(evt *gomatrix.Event) messages.UIMessage { - sender, text := getMembershipEventContent(evt) +func ParseMembershipEvent(room *rooms.Room, evt *gomatrix.Event) messages.UIMessage { + displayname, text := getMembershipEventContent(room, evt) ts := unixToTime(evt.Timestamp) - return messages.NewExpandedTextMessage(evt.ID, sender, "m.room.membership", text, ts) + return messages.NewExpandedTextMessage(evt.ID, evt.Sender, displayname, "m.room.membership", text, ts) } diff --git a/ui/messages/textbase.go b/ui/messages/textbase.go index d7eb16c..0960a57 100644 --- a/ui/messages/textbase.go +++ b/ui/messages/textbase.go @@ -32,8 +32,8 @@ type BaseTextMessage struct { BaseMessage } -func newBaseTextMessage(id, sender, msgtype string, timestamp time.Time) BaseTextMessage { - return BaseTextMessage{newBaseMessage(id, sender, msgtype, timestamp)} +func newBaseTextMessage(id, sender, displayname, msgtype string, timestamp time.Time) BaseTextMessage { + return BaseTextMessage{newBaseMessage(id, sender, displayname, msgtype, timestamp)} } // Regular expressions used to split lines when calculating the buffer. diff --git a/ui/messages/textmessage.go b/ui/messages/textmessage.go index 4c99e5b..489e8a7 100644 --- a/ui/messages/textmessage.go +++ b/ui/messages/textmessage.go @@ -36,9 +36,9 @@ type TextMessage struct { } // NewTextMessage creates a new UITextMessage object with the provided values and the default state. -func NewTextMessage(id, sender, msgtype, text string, timestamp time.Time) UIMessage { +func NewTextMessage(id, sender, displayname, msgtype, text string, timestamp time.Time) UIMessage { return &TextMessage{ - BaseTextMessage: newBaseTextMessage(id, sender, msgtype, timestamp), + BaseTextMessage: newBaseTextMessage(id, sender, displayname, msgtype, timestamp), MsgText: text, } } diff --git a/ui/room-view.go b/ui/room-view.go index d7824fe..d38db94 100644 --- a/ui/room-view.go +++ b/ui/room-view.go @@ -23,11 +23,11 @@ import ( "strings" "time" - "maunium.net/go/tcell" "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/ui/messages" "maunium.net/go/gomuks/ui/widget" + "maunium.net/go/tcell" "maunium.net/go/tview" ) @@ -213,16 +213,15 @@ func (view *RoomView) SetTyping(users []string) { } } -func (view *RoomView) AutocompleteUser(existingText string) (completions []string) { +func (view *RoomView) AutocompleteUser(existingText string) (completions []*rooms.Member) { textWithoutPrefix := existingText if strings.HasPrefix(existingText, "@") { textWithoutPrefix = existingText[1:] } for _, user := range view.Room.GetMembers() { - if strings.HasPrefix(user.DisplayName, textWithoutPrefix) { - completions = append(completions, user.DisplayName) - } else if strings.HasPrefix(user.UserID, existingText) { - completions = append(completions, user.UserID) + if strings.HasPrefix(user.DisplayName, textWithoutPrefix) || + strings.HasPrefix(user.UserID, existingText) { + completions = append(completions, user) } } return @@ -257,10 +256,12 @@ func (view *RoomView) UpdateUserList() { func (view *RoomView) newUIMessage(id, sender, msgtype, text string, timestamp time.Time) messages.UIMessage { member := view.Room.GetMember(sender) + displayname := sender if member != nil { - sender = member.DisplayName + displayname = member.DisplayName } - return messages.NewTextMessage(id, sender, msgtype, text, timestamp) + msg := messages.NewTextMessage(id, sender, displayname, msgtype, text, timestamp) + return msg } func (view *RoomView) NewMessage(id, sender, msgtype, text string, timestamp time.Time) ifc.Message { diff --git a/ui/view-main.go b/ui/view-main.go index d4ffd39..018fb1a 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -104,17 +104,22 @@ func findWordToTabComplete(text string) string { func (view *MainView) InputTabComplete(roomView *RoomView, text string, cursorOffset int) string { str := runewidth.Truncate(text, cursorOffset, "") word := findWordToTabComplete(str) + userCompletions := roomView.AutocompleteUser(word) if len(userCompletions) == 1 { startIndex := len(str) - len(word) - completion := userCompletions[0] + member := userCompletions[0] + completion := fmt.Sprintf("[%s](https://matrix.to/#/%s)", member.DisplayName, member.UserID) if startIndex == 0 { completion = completion + ": " } text = str[0:startIndex] + completion + text[len(str):] - } else if len(userCompletions) > 1 && len(userCompletions) < 6 { - roomView.SetStatus(fmt.Sprintf("Completions: %s", strings.Join(userCompletions, ", "))) + } else if len(userCompletions) > 1 && len(userCompletions) <= 5 { + // roomView.SetStatus(fmt.Sprintf("Completions: %s", strings.Join(userCompletions, ", "))) + } else if len(userCompletions) > 5 { + roomView.SetStatus("Over 5 completion options.") } + return text } -- cgit v1.2.3