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>
}
|