From d675f3b3f36f5bae54733ee7534501c518e78351 Mon Sep 17 00:00:00 2001
From: nbsp <nbsp@tilde.town>
Date: Sat, 22 Mar 2025 14:32:46 +0200
Subject: [PATCH] search

---
 app/app.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++------
 go.mod     |  3 ++-
 go.sum     |  2 ++
 ui/list.go |  4 +++
 4 files changed, 79 insertions(+), 9 deletions(-)

diff --git a/app/app.go b/app/app.go
index efa48ae..fb54472 100644
--- a/app/app.go
+++ b/app/app.go
@@ -2,13 +2,18 @@ package app
 
 import (
 	"os"
+	"strings"
 
 	"git.sr.ht/~rockorager/vaxis"
+	"git.sr.ht/~rockorager/vaxis/widgets/textinput"
 	"git.tilde.town/nbsp/directory/ui"
 )
 
 type App struct {
-	users ui.List
+	allUsers  []string
+	users     ui.List
+	filter    textinput.Model
+	searching bool
 }
 
 func New() *App {
@@ -19,24 +24,82 @@ func New() *App {
 			users = append(users, "~"+user.Name())
 		}
 	}
-	return &App{ui.NewList(users, 0)}
+	return &App{users: ui.NewList(users, 0), allUsers: users}
 }
 
 func (app *App) Event(state *ui.State, event vaxis.Event) {
 	if key, ok := event.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
+		other := false
 		switch key.String() {
-		case "Ctrl+c", "Ctrl+d", "q":
+		case "Ctrl+c", "Ctrl+d":
 			close(ui.Quit)
-		case "Down", "j", "Ctrl+n":
+		case "Down", "Ctrl+n":
 			app.users.Down()
-		case "Up", "k", "Ctrl+p":
+		case "Up", "Ctrl+p":
 			app.users.Up()
-		case "End", "Shift+g":
+		case "End":
 			app.users.End()
-		case "Home", "g":
+		case "Home":
 			app.users.Home()
+		case "Escape":
+			app.searching = false
+		default:
+			other = true
+		}
+		if other {
+			if !app.searching {
+				switch key.String() {
+				case "q":
+					close(ui.Quit)
+				case "j":
+					app.users.Down()
+				case "k":
+					app.users.Up()
+				case "g":
+					app.users.Home()
+				case "Shift+g":
+					app.users.End()
+				case "/":
+					app.searching = true
+					app.filter.SetContent("")
+				}
+			} else {
+				app.filter.Update(event)
+				app.users.SetItems(filter(app.allUsers, app.filter.String()))
+			}
 		}
 	}
 
-	app.users.Draw(state.Window().New(0, 0, 20, state.Window().Height))
+	win := state.Window()
+	w, h := win.Size()
+	if len(app.users.Items()) > 0 {
+		app.users.Draw(win.New(0, 0, 20, h-1))
+	}
+	for x := 0; x < w; x++ {
+		setCell(win, x, h-2, '─', vaxis.Style{})
+	}
+	if app.searching {
+		app.filter.Draw(win.New(0, h-1, w, 1))
+	} else {
+		win.New(0, h-1, w, 1).Print(vaxis.Segment{Text: "press / to search users", Style: vaxis.Style{Foreground: vaxis.IndexColor(8)}})
+	}
+}
+
+func setCell(win vaxis.Window, x int, y int, r rune, st vaxis.Style) {
+	win.SetCell(x, y, vaxis.Cell{
+		Character: vaxis.Character{
+			Grapheme: string([]rune{r}),
+		},
+		Style: st,
+	})
+}
+
+func filter(users []string, query string) []string {
+	var filtered []string
+	for _, s := range users {
+		if strings.Contains(s, query) {
+			filtered = append(filtered, s)
+		}
+	}
+	return filtered
 }
diff --git a/go.mod b/go.mod
index fe1bd0e..93685a2 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module git.tilde.town/nbsp/directory
 
-go 1.23.5
+go 1.23
 
 require git.sr.ht/~rockorager/vaxis v0.13.0
 
@@ -10,6 +10,7 @@ require (
 	github.com/mattn/go-sixel v0.0.5 // indirect
 	github.com/rivo/uniseg v0.4.4 // indirect
 	github.com/soniakeys/quant v1.0.0 // indirect
+	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
 	golang.org/x/image v0.9.0 // indirect
 	golang.org/x/sys v0.10.0 // indirect
 )
diff --git a/go.sum b/go.sum
index cec6c31..c75b49d 100644
--- a/go.sum
+++ b/go.sum
@@ -14,6 +14,8 @@ github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsIm
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
+golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g=
 golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
diff --git a/ui/list.go b/ui/list.go
index e5d1591..9ba8d95 100644
--- a/ui/list.go
+++ b/ui/list.go
@@ -32,6 +32,10 @@ func (m *List) Draw(win vaxis.Window) {
 		m.offset = m.index
 	}
 
+	if m.offset < 0 {
+		m.offset = 0
+	}
+
 	defaultStyle := vaxis.Style{}
 	selectedStyle := vaxis.Style{Attribute: vaxis.AttrReverse}