From cce79ab7d8481a06054166049876c8a2cbb3418f Mon Sep 17 00:00:00 2001
From: Tulir Asokan <tulir@maunium.net>
Date: Tue, 22 May 2018 17:23:54 +0300
Subject: Clean up code

---
 matrix/matrix.go              |  98 +++++++++++++-----------------
 ui/fuzzy-search-modal.go      | 136 ++++++++++++++++++++++++++++++++++++++++++
 ui/fuzzy-view.go              | 127 ---------------------------------------
 ui/messages/textbase.go       |  26 ++++----
 ui/messages/tstring/string.go |  17 +++---
 ui/view-main.go               |   6 +-
 6 files changed, 204 insertions(+), 206 deletions(-)
 create mode 100644 ui/fuzzy-search-modal.go
 delete mode 100644 ui/fuzzy-view.go

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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
-
-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
 		}
-- 
cgit v1.2.3-70-g09d2