diff --git a/matrix/matrix.go b/matrix/matrix.go
index c41975e..264cff8 100644
--- a/matrix/matrix.go
+++ b/matrix/matrix.go
@@ -254,6 +254,47 @@ func (c *Container) HandleMessage(source EventSource, evt *gomatrix.Event) {
}
}
+// HandleMembership is the event handler for the m.room.member state event.
+func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) {
+ isLeave := source&EventSourceLeave != 0
+ isTimeline := source&EventSourceTimeline != 0
+ isNonTimelineLeave := isLeave && !isTimeline
+ if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave {
+ return
+ } else if evt.StateKey != nil && *evt.StateKey == c.config.UserID {
+ c.processOwnMembershipChange(evt)
+ } else if !isTimeline && (!c.config.AuthCache.InitialSyncDone || isLeave) {
+ // We don't care about other users' membership events in the initial sync or chats we've left.
+ return
+ }
+
+ c.HandleMessage(source, evt)
+}
+
+func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
+ membership, _ := evt.Content["membership"].(string)
+ prevMembership := "leave"
+ if evt.Unsigned.PrevContent != nil {
+ prevMembership, _ = evt.Unsigned.PrevContent["membership"].(string)
+ }
+ debug.Printf("Processing own membership change: %s->%s in %s", prevMembership, membership, evt.RoomID)
+ if membership == prevMembership {
+ return
+ }
+ room := c.GetRoom(evt.RoomID)
+ switch membership {
+ case "join":
+ c.ui.MainView().AddRoom(room)
+ room.HasLeft = false
+ case "leave":
+ c.ui.MainView().RemoveRoom(room)
+ room.HasLeft = true
+ case "invite":
+ // TODO handle
+ debug.Printf("%s invited the user to %s", evt.Sender, evt.RoomID)
+ }
+}
+
func (c *Container) parseReadReceipt(evt *gomatrix.Event) (largestTimestampEvent string) {
var largestTimestamp int64
for eventID, rawContent := range evt.Content {
@@ -368,63 +409,6 @@ func (c *Container) HandleTag(source EventSource, evt *gomatrix.Event) {
mainView.UpdateTags(room)
}
-func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {
- membership, _ := evt.Content["membership"].(string)
- prevMembership := "leave"
- if evt.Unsigned.PrevContent != nil {
- prevMembership, _ = evt.Unsigned.PrevContent["membership"].(string)
- }
- debug.Printf("Processing own membership change: %s->%s in %s", prevMembership, membership, evt.RoomID)
- if membership == prevMembership {
- return
- }
- room := c.GetRoom(evt.RoomID)
- switch membership {
- case "join":
- c.ui.MainView().AddRoom(room)
- room.HasLeft = false
- case "leave":
- c.ui.MainView().RemoveRoom(room)
- room.HasLeft = true
- case "invite":
- // TODO handle
- debug.Printf("%s invited the user to %s", evt.Sender, evt.RoomID)
- }
-}
-
-// HandleMembership is the event handler for the m.room.member state event.
-func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) {
- isLeave := source&EventSourceLeave != 0
- isTimeline := source&EventSourceTimeline != 0
- isNonTimelineLeave := isLeave && !isTimeline
- if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave {
- return
- } else if evt.StateKey != nil && *evt.StateKey == c.config.UserID {
- c.processOwnMembershipChange(evt)
- } else if !isTimeline && (!c.config.AuthCache.InitialSyncDone || isLeave) {
- // We don't care about other users' membership events in the initial sync or chats we've left.
- return
- }
-
- mainView := c.ui.MainView()
- roomView := mainView.GetRoom(evt.RoomID)
- if roomView == nil {
- return
- }
-
- message := mainView.ParseEvent(roomView, evt)
- if message != nil {
- roomView.AddMessage(message, ifc.AppendMessage)
- roomView.MxRoom().LastReceivedMessage = message.Timestamp()
- // We don't want notifications at startup.
- if c.syncer.FirstSyncDone {
- pushRules := c.PushRules().GetActions(roomView.MxRoom(), evt).Should()
- mainView.NotifyMessage(roomView.MxRoom(), message, pushRules)
- c.ui.Render()
- }
- }
-}
-
// HandleTyping is the event handler for the m.typing event.
func (c *Container) HandleTyping(source EventSource, evt *gomatrix.Event) {
users := evt.Content["user_ids"].([]interface{})
diff --git a/ui/fuzzy-search-modal.go b/ui/fuzzy-search-modal.go
new file mode 100644
index 0000000..4b77ca8
--- /dev/null
+++ b/ui/fuzzy-search-modal.go
@@ -0,0 +1,136 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package ui
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+
+ "github.com/evidlo/fuzzysearch/fuzzy"
+ "maunium.net/go/gomuks/matrix/rooms"
+ "maunium.net/go/gomuks/ui/widget"
+ "maunium.net/go/tview"
+ "maunium.net/go/tcell"
+ "maunium.net/go/gomuks/debug"
+)
+
+type FuzzySearchModal struct {
+ tview.Primitive
+
+ search *tview.InputField
+ results *tview.TextView
+
+ matches fuzzy.Ranks
+ selected int
+
+ roomList []*rooms.Room
+ roomTitles []string
+
+ parent *GomuksUI
+ mainView *MainView
+}
+
+func NewFuzzySearchModal(mainView *MainView, width int, height int) *FuzzySearchModal {
+ fs := &FuzzySearchModal{
+ parent: mainView.parent,
+ mainView: mainView,
+ }
+
+ fs.InitList(mainView.rooms)
+
+ fs.search = tview.NewInputField().
+ SetLabel("Room: ")
+ fs.search.
+ SetChangedFunc(fs.changeHandler).
+ SetInputCapture(fs.keyHandler)
+
+ fs.results = tview.NewTextView().
+ SetDynamicColors(true).
+ SetRegions(true)
+ fs.results.SetBorderPadding(1, 0, 0, 0)
+
+ // Flex widget containing input box and results
+ container := tview.NewFlex().
+ SetDirection(tview.FlexRow).
+ AddItem(fs.search, 1, 0, true).
+ AddItem(fs.results, 0, 1, false)
+ container.
+ SetBorder(true).
+ SetBorderPadding(1, 1, 1, 1).
+ SetTitle("Quick Room Switcher")
+
+ fs.Primitive = widget.TransparentCenter(width, height, container)
+
+ return fs
+}
+
+func (fs *FuzzySearchModal) InitList(rooms map[string]*RoomView) {
+ for _, room := range rooms {
+ fs.roomList = append(fs.roomList, room.Room)
+ fs.roomTitles = append(fs.roomTitles, room.Room.GetTitle())
+ }
+}
+
+func (fs *FuzzySearchModal) changeHandler(str string) {
+ // Get matches and display in result box
+ fs.matches = fuzzy.RankFindFold(str, fs.roomTitles)
+ if len(str) > 0 && len(fs.matches) > 0 {
+ sort.Sort(fs.matches)
+ fs.results.Clear()
+ for _, match := range fs.matches {
+ fmt.Fprintf(fs.results, `["%d"]%s[""]\n`, match.Index, match.Target)
+ }
+ fs.parent.Render()
+ fs.results.Highlight(strconv.Itoa(fs.matches[0].Index))
+ fs.results.ScrollToBeginning()
+ } else {
+ fs.results.Clear()
+ fs.results.Highlight()
+ }
+}
+
+func (fs *FuzzySearchModal) keyHandler(event *tcell.EventKey) *tcell.EventKey {
+ highlights := fs.results.GetHighlights()
+ switch event.Key() {
+ case tcell.KeyEsc:
+ // Close room finder
+ fs.parent.views.RemovePage("fuzzy-search-modal")
+ fs.parent.app.SetFocus(fs.parent.views)
+ return nil
+ case tcell.KeyTab:
+ // Cycle highlighted area to next match
+ if len(highlights) > 0 {
+ fs.selected = (fs.selected + 1) % len(fs.matches)
+ fs.results.Highlight(strconv.Itoa(fs.matches[fs.selected].Index))
+ fs.results.ScrollToHighlight()
+ }
+ return nil
+ case tcell.KeyEnter:
+ // Switch room to currently selected room
+ if len(highlights) > 0 {
+ debug.Print("Fuzzy Selected Room:", fs.roomList[fs.matches[fs.selected].Index].GetTitle())
+ fs.mainView.SwitchRoom(fs.roomList[fs.matches[fs.selected].Index].Tags()[0].Tag, fs.roomList[fs.matches[fs.selected].Index])
+ }
+ fs.parent.views.RemovePage("fuzzy-search-modal")
+ fs.parent.app.SetFocus(fs.parent.views)
+ fs.results.Clear()
+ fs.search.SetText("")
+ return nil
+ }
+ return event
+}
diff --git a/ui/fuzzy-view.go b/ui/fuzzy-view.go
deleted file mode 100644
index d5498d0..0000000
--- a/ui/fuzzy-view.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// gomuks - A terminal Matrix client written in Go.
-// Copyright (C) 2018 Tulir Asokan
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-package ui
-
-import (
- "fmt"
- "sort"
- "strconv"
-
- "github.com/evidlo/fuzzysearch/fuzzy"
- "maunium.net/go/gomuks/debug"
- "maunium.net/go/gomuks/matrix/rooms"
- "maunium.net/go/gomuks/ui/widget"
- "maunium.net/go/tcell"
- "maunium.net/go/tview"
-)
-
-type FuzzyView struct {
- tview.Primitive
- matches fuzzy.Ranks
- selected int
-}
-
-func NewFuzzyView(view *MainView, width int, height int) *FuzzyView {
-
- roomList := []*rooms.Room{}
- roomTitles := []string{}
- for _, tag := range view.roomList.tags {
- for _, room := range view.roomList.items[tag].rooms {
- roomList = append(roomList, room.Room)
- roomTitles = append(roomTitles, room.GetTitle())
- }
- }
- // search box for fuzzy search
- fuzzySearch := tview.NewInputField().
- SetLabel("Room: ")
-
- // list of rooms matching fuzzy search
- fuzzyResults := tview.NewTextView().
- SetDynamicColors(true).
- SetRegions(true)
-
- fuzzyResults.
- SetBorderPadding(1, 0, 0, 0)
-
- // flexbox containing input box and results
- fuzzyFlex := tview.NewFlex().
- SetDirection(tview.FlexRow).
- AddItem(fuzzySearch, 1, 0, true).
- AddItem(fuzzyResults, 0, 1, false)
-
- fuzzyFlex.SetBorder(true).
- SetBorderPadding(1, 1, 1, 1).
- SetTitle("Fuzzy Room Finder")
-
- var matches fuzzy.Ranks
- var selected int
- fuzz := &FuzzyView{
- Primitive: widget.TransparentCenter(width, height, fuzzyFlex),
- matches: matches,
- selected: selected,
- }
-
- // callback to update search box
- fuzzySearch.SetChangedFunc(func(str string) {
- // get matches and display in fuzzyResults
- fuzz.matches = fuzzy.RankFindFold(str, roomTitles)
- if len(str) > 0 && len(fuzz.matches) > 0 {
- sort.Sort(fuzz.matches)
- fuzzyResults.Clear()
- for _, match := range fuzz.matches {
- fmt.Fprintf(fuzzyResults, "[\"%d\"]%s[\"\"]\n", match.Index, match.Target)
- }
- view.parent.app.Draw()
- fuzzyResults.Highlight(strconv.Itoa(fuzz.matches[0].Index))
- fuzzyResults.ScrollToBeginning()
- } else {
- fuzzyResults.Clear()
- fuzzyResults.Highlight()
- }
- })
-
- // callback to handle key events on fuzzy search
- fuzzySearch.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
- highlights := fuzzyResults.GetHighlights()
- if event.Key() == tcell.KeyEsc {
- view.parent.views.RemovePage("fuzzy")
- view.parent.app.SetFocus(view.parent.views)
- return nil
- } else if event.Key() == tcell.KeyTab {
- // cycle highlighted area to next fuzzy match
- if len(highlights) > 0 {
- fuzz.selected = (fuzz.selected + 1) % len(fuzz.matches)
- fuzzyResults.Highlight(strconv.Itoa(fuzz.matches[fuzz.selected].Index))
- fuzzyResults.ScrollToHighlight()
- }
- return nil
- } else if event.Key() == tcell.KeyEnter {
- // switch room to currently selected room
- if len(highlights) > 0 {
- debug.Print("Fuzzy Selected Room:", roomList[fuzz.matches[fuzz.selected].Index].GetTitle())
- view.SwitchRoom(roomList[fuzz.matches[fuzz.selected].Index].Tags()[0].Tag, roomList[fuzz.matches[fuzz.selected].Index])
- }
- view.parent.views.RemovePage("fuzzy")
- fuzzyResults.Clear()
- fuzzySearch.SetText("")
- return nil
- }
- return event
- })
-
- return fuzz
-}
diff --git a/ui/messages/textbase.go b/ui/messages/textbase.go
index c241c0a..f805067 100644
--- a/ui/messages/textbase.go
+++ b/ui/messages/textbase.go
@@ -44,6 +44,18 @@ var (
spacePattern = regexp.MustCompile(`\s+`)
)
+func matchBoundaryPattern(extract tstring.TString) tstring.TString {
+ matches := boundaryPattern.FindAllStringIndex(extract.String(), -1)
+ if len(matches) > 0 {
+ if match := matches[len(matches)-1]; len(match) >= 2 {
+ if until := match[1]; until < len(extract) {
+ extract = extract[:until]
+ }
+ }
+ }
+ return extract
+}
+
// CalculateBuffer generates the internal buffer for this message that consists
// of the text of this message split into lines at most as wide as the width
// parameter.
@@ -63,24 +75,14 @@ func (msg *BaseTextMessage) calculateBufferWithText(text tstring.TString, width
} else {
newlines = 0
}
- // Mostly from tview/textview.go#reindexBuffer()
+ // Adapted from tview/textview.go#reindexBuffer()
for len(str) > 0 {
extract := str.Truncate(width)
if len(extract) < len(str) {
if spaces := spacePattern.FindStringIndex(str[len(extract):].String()); spaces != nil && spaces[0] == 0 {
extract = str[:len(extract)+spaces[1]]
}
-
- matches := boundaryPattern.FindAllStringIndex(extract.String(), -1)
- if len(matches) > 0 {
- match := matches[len(matches)-1]
- if len(match) >= 2 {
- until := match[1]
- if until < len(extract) {
- extract = extract[:until]
- }
- }
- }
+ extract = matchBoundaryPattern(extract)
}
msg.buffer = append(msg.buffer, extract)
str = str[len(extract):]
diff --git a/ui/messages/tstring/string.go b/ui/messages/tstring/string.go
index a87d16a..4f3ee29 100644
--- a/ui/messages/tstring/string.go
+++ b/ui/messages/tstring/string.go
@@ -67,19 +67,22 @@ func (str TString) Append(data string) TString {
}
func (str TString) AppendColor(data string, color tcell.Color) TString {
- newStr := make(TString, len(str)+len(data))
- copy(newStr, str)
- for i, char := range data {
- newStr[i+len(str)] = NewColorCell(char, color)
- }
- return newStr
+ return str.AppendCustom(data, func(r rune) Cell {
+ return NewColorCell(r, color)
+ })
}
func (str TString) AppendStyle(data string, style tcell.Style) TString {
+ return str.AppendCustom(data, func(r rune) Cell {
+ return NewStyleCell(r, style)
+ })
+}
+
+func (str TString) AppendCustom(data string, cellCreator func(rune) Cell) TString {
newStr := make(TString, len(str)+len(data))
copy(newStr, str)
for i, char := range data {
- newStr[i+len(str)] = NewStyleCell(char, style)
+ newStr[i+len(str)] = cellCreator(char)
}
return newStr
}
diff --git a/ui/view-main.go b/ui/view-main.go
index e576a24..74a6ce1 100644
--- a/ui/view-main.go
+++ b/ui/view-main.go
@@ -202,9 +202,9 @@ func (view *MainView) KeyEventHandler(roomView *RoomView, key *tcell.EventKey) *
case tcell.KeyUp:
view.SwitchRoom(view.roomList.Previous())
case tcell.KeyEnter:
- fuzz := NewFuzzyView(view, 42, 12)
- view.parent.views.AddPage("fuzzy", fuzz, true, true)
- view.parent.app.SetFocus(fuzz)
+ searchModal := NewFuzzySearchModal(view, 42, 12)
+ view.parent.views.AddPage("fuzzy-search-modal", searchModal, true, true)
+ view.parent.app.SetFocus(searchModal)
default:
return key
}