wip: underline filtered items

Ayman Bagabas created

Change summary

go.sum                             |  2 
ui/components/selector/selector.go |  2 
ui/pages/selection/item.go         | 71 ++++++++++++++++++++++---------
ui/pages/selection/selection.go    | 22 ++++----
4 files changed, 61 insertions(+), 36 deletions(-)

Detailed changes

go.sum 🔗

@@ -17,8 +17,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
 github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/aymanbagabas/go-osc52 v1.0.0 h1:yjRSILSEUSIhuRcyOJ55tbSKtvBLmJDgbPErH8pXcxM=
-github.com/aymanbagabas/go-osc52 v1.0.0/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
 github.com/aymanbagabas/go-osc52 v1.0.1 h1:juDXgeKhMfVnylcoA4S7p9E4q+9DErUZGkX8t2ZR2j8=
 github.com/aymanbagabas/go-osc52 v1.0.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=

ui/components/selector/selector.go 🔗

@@ -17,7 +17,7 @@ type Selector struct {
 
 // IdentifiableItem is an item that can be identified by a string and extends list.Item.
 type IdentifiableItem interface {
-	list.Item
+	list.DefaultItem
 	ID() string
 }
 

ui/pages/selection/item.go 🔗

@@ -16,20 +16,26 @@ import (
 
 // Item represents a single item in the selector.
 type Item struct {
-	Title       string
-	Name        string
-	Description string
-	LastUpdate  time.Time
-	URL         *yankable.Yankable
+	name       string
+	repo       string
+	desc       string
+	lastUpdate time.Time
+	url        *yankable.Yankable
 }
 
 // ID implements selector.IdentifiableItem.
 func (i Item) ID() string {
-	return i.Name
+	return i.repo
 }
 
+// Title returns the item title. Implements list.DefaultItem.
+func (i Item) Title() string { return i.name }
+
+// Description returns the item description. Implements list.DefaultItem.
+func (i Item) Description() string { return i.desc }
+
 // FilterValue implements list.Item.
-func (i Item) FilterValue() string { return i.Title }
+func (i Item) FilterValue() string { return i.name }
 
 // ItemDelegate is the delegate for the item.
 type ItemDelegate struct {
@@ -77,8 +83,8 @@ func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
 				continue
 			}
 		}
-		y, cmd := itm.URL.Update(msg)
-		itm.URL = y.(*yankable.Yankable)
+		y, cmd := itm.url.Update(msg)
+		itm.url = y.(*yankable.Yankable)
 		if cmd != nil {
 			cmds = append(cmds, cmd)
 		}
@@ -90,29 +96,50 @@ func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
 func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
 	i := listItem.(Item)
 	s := strings.Builder{}
-	style := d.styles.MenuItem.Copy()
-	if index == m.Index() {
-		style = style.BorderForeground(d.styles.ActiveBorderColor)
+	var matchedRunes []int
+
+	// Conditions
+	var (
+		isSelected = index == m.Index()
+		// emptyFilter = m.FilterState() == list.Filtering && m.FilterValue() == ""
+		isFiltered = m.FilterState() == list.Filtering || m.FilterState() == list.FilterApplied
+	)
+
+	itemStyle := d.styles.MenuItem.Copy()
+	if isSelected {
+		itemStyle = itemStyle.BorderForeground(d.styles.ActiveBorderColor)
 		if d.activeBox != nil && *d.activeBox == readmeBox {
 			// TODO make this into its own color
-			style = style.BorderForeground(lipgloss.Color("15"))
+			itemStyle = itemStyle.BorderForeground(lipgloss.Color("15"))
 		}
 	}
-	titleStr := i.Title
-	updatedStr := fmt.Sprintf(" Updated %s", humanize.Time(i.LastUpdate))
+
+	title := i.name
+	updatedStr := fmt.Sprintf(" Updated %s", humanize.Time(i.lastUpdate))
 	updated := d.styles.MenuLastUpdate.
 		Copy().
-		Width(m.Width() - style.GetHorizontalFrameSize() - lipgloss.Width(titleStr)).
+		Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(title)).
 		Render(updatedStr)
-	title := lipgloss.NewStyle().
+	titleStyle := lipgloss.NewStyle().
 		Align(lipgloss.Left).
-		Width(m.Width() - style.GetHorizontalFrameSize() - lipgloss.Width(updated)).
-		Render(titleStr)
+		Width(m.Width() - itemStyle.GetHorizontalFrameSize() - lipgloss.Width(updated))
+
+	if isFiltered && index < len(m.VisibleItems()) {
+		// Get indices of matched characters
+		matchedRunes = m.MatchesForItem(index)
+	}
+
+	if isFiltered {
+		unmatched := lipgloss.NewStyle().Inline(true)
+		matched := unmatched.Copy().Underline(true)
+		title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
+	}
+	title = titleStyle.Render(title)
 
 	s.WriteString(lipgloss.JoinHorizontal(lipgloss.Bottom, title, updated))
 	s.WriteString("\n")
-	s.WriteString(i.Description)
+	s.WriteString(i.desc)
 	s.WriteString("\n\n")
-	s.WriteString(i.URL.View())
-	w.Write([]byte(style.Render(s.String())))
+	s.WriteString(i.url.View())
+	w.Write([]byte(itemStyle.Render(s.String())))
 }

ui/pages/selection/selection.go 🔗

@@ -140,29 +140,29 @@ func (s *Selection) Init() tea.Cmd {
 	// Put configured repos first
 	for _, r := range cfg.Repos {
 		items = append(items, Item{
-			Title:       r.Name,
-			Name:        r.Repo,
-			Description: r.Note,
-			LastUpdate:  time.Now(),
-			URL:         yank(repoUrl(cfg, r.Repo)),
+			name:       r.Name,
+			repo:       r.Repo,
+			desc:       r.Note,
+			lastUpdate: time.Now(), // TODO get repo last update
+			url:        yank(repoUrl(cfg, r.Repo)),
 		})
 	}
 	for _, r := range cfg.Source.AllRepos() {
 		exists := false
 		for _, item := range items {
 			item := item.(Item)
-			if item.Name == r.Name() {
+			if item.repo == r.Name() {
 				exists = true
 				break
 			}
 		}
 		if !exists {
 			items = append(items, Item{
-				Title:       r.Name(),
-				Name:        r.Name(),
-				Description: "",
-				LastUpdate:  time.Now(),
-				URL:         yank(repoUrl(cfg, r.Name())),
+				name:       r.Name(),
+				repo:       r.Name(),
+				desc:       "",
+				lastUpdate: time.Now(), // TODO get repo last update
+				url:        yank(repoUrl(cfg, r.Name())),
 			})
 		}
 	}