gitstack

grindlemire/go-tui code browser

2.8 KB 119 lines 2026-03-15 · e5befc1 raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package main

import (
	"fmt"
	"strings"
	tui "github.com/grindlemire/go-tui"
)

type searchBar struct {
	active *tui.State[bool]
	query  *tui.State[string]
}

func SearchBar(active *tui.State[bool], query *tui.State[string]) *searchBar {
	return &searchBar{active: active, query: query}
}

func (s *searchBar) KeyMap() tui.KeyMap {
	if !s.active.Get() {
		return nil
	}
	return tui.KeyMap{
		tui.OnStop(tui.AnyRune, s.appendChar),
		tui.OnStop(tui.KeyBackspace, s.deleteChar),
		tui.OnStop(tui.KeyEnter, s.submit),
		tui.OnStop(tui.KeyEscape, s.deactivate),
	}
}

func (s *searchBar) appendChar(ke tui.KeyEvent) {
	s.query.Set(s.query.Get() + string(ke.Rune))
}

func (s *searchBar) deleteChar(ke tui.KeyEvent) {
	q := s.query.Get()
	if len(q) > 0 {
		s.query.Set(q[:len(q)-1])
	}
}

func (s *searchBar) submit(ke tui.KeyEvent) {
	s.active.Set(false)
}

func (s *searchBar) deactivate(ke tui.KeyEvent) {
	s.active.Set(false)
	s.query.Set("")
}

templ (s *searchBar) Render() {
	<div class="shrink-0">
		if s.active.Get() {
			<hr />
			<div class="px-1 flex gap-1">
				<span class="text-cyan font-bold">Search:</span>
				<span>{s.query.Get()}</span>
				<span class="text-cyan blink">_</span>
			</div>
		}
	</div>
}

// Content displays files for the selected category
type content struct {
	category *tui.State[string]
	query    *tui.State[string]
}

func Content(category *tui.State[string], query *tui.State[string]) *content {
	return &content{category: category, query: query}
}

var filesByCategory = map[string][]string{
	"Documents": {"report.pdf", "notes.md", "budget.xlsx", "readme.txt", "design.doc"},
	"Images":    {"photo.jpg", "screenshot.png", "logo.svg", "banner.gif", "icon.ico"},
	"Music":     {"song.mp3", "album.flac", "podcast.ogg", "ringtone.wav"},
	"Projects":  {"go-tui/", "website/", "api-server/", "mobile-app/", "scripts/"},
	"Downloads": {"setup.exe", "archive.zip", "data.csv", "patch-v2.tar.gz"},
}

func (c *content) filteredFiles() []string {
	cat := c.category.Get()
	files, ok := filesByCategory[cat]
	if !ok {
		return nil
	}
	q := strings.ToLower(c.query.Get())
	if q == "" {
		return files
	}
	var result []string
	for _, f := range files {
		if strings.Contains(strings.ToLower(f), q) {
			result = append(result, f)
		}
	}
	return result
}

templ (c *content) Render() {
	<div class="flex-col grow px-2 overflow-hidden">
		<span class="font-bold text-cyan">{c.category.Get() + "/"}</span>
		<hr />
		for i, file := range c.filteredFiles() {
			if i == len(c.filteredFiles())-1 {
				<span>{fmt.Sprintf("└── %s", file)}</span>
			} else {
				<span>{fmt.Sprintf("├── %s", file)}</span>
			}
		}
		if len(c.filteredFiles()) == 0 {
			<span class="font-dim">No matching files</span>
		}
		if c.query.Get() != "" {
			<br />
			<span class="font-dim">{fmt.Sprintf("Filtering: \"%s\"", c.query.Get())}</span>
		}
	</div>
}