summaryrefslogtreecommitdiff
path: root/internal/tui/eventstream/searchmodal.go
blob: c09542b39fba1472d2ae6b8bd4acd33a37c25068 (plain)
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
120
121
122
123
124
125
126
127
128
package eventstream

import (
	"strings"

	"charm.land/bubbles/v2/textinput"
	tea "charm.land/bubbletea/v2"
	"charm.land/lipgloss/v2"
)

type SearchDirection int

const (
	SearchForward  SearchDirection = 1
	SearchBackward SearchDirection = -1
)

type SearchModal struct {
	visible   bool
	textInput textinput.Model
	err       string
	direction SearchDirection
}

func NewSearchModal() SearchModal {
	input := textinput.New()
	input.Prompt = ""
	input.CharLimit = 0
	input.SetWidth(44)
	input.SetStyles(textinput.DefaultStyles(true))
	return SearchModal{textInput: input, direction: SearchForward}
}

func (m SearchModal) Visible() bool {
	return m.visible
}

func (m SearchModal) Direction() SearchDirection {
	return m.direction
}

// SetDarkMode updates search modal text input styles.
func (m SearchModal) SetDarkMode(isDark bool) SearchModal {
	m.textInput.SetStyles(textinput.DefaultStyles(isDark))
	return m
}

func (m SearchModal) Open(direction SearchDirection, defaultTerm string) SearchModal {
	m.visible = true
	m.err = ""
	m.direction = direction
	m.textInput.SetValue(defaultTerm)
	m.textInput.CursorEnd()
	m.textInput.Focus()
	return m
}

func (m SearchModal) Close() SearchModal {
	m.visible = false
	m.err = ""
	m.textInput.Blur()
	return m
}

// Update returns updated modal, submitted term, and whether submit occurred.
func (m SearchModal) Update(msg tea.Msg) (SearchModal, string, bool) {
	if !m.visible {
		return m, "", false
	}
	if keyMsg, ok := msg.(tea.KeyPressMsg); ok {
		switch keyMsg.String() {
		case "esc":
			return m.Close(), "", false
		case "enter":
			term := strings.TrimSpace(m.textInput.Value())
			if term == "" {
				m.err = "search term is required"
				return m, "", false
			}
			return m.Close(), term, true
		}
	}
	var cmd tea.Cmd
	m.textInput, cmd = m.textInput.Update(msg)
	_ = cmd
	return m, "", false
}

func (m SearchModal) View(width, height int) string {
	if !m.visible {
		return ""
	}
	if width <= 0 {
		width = 80
	}
	if height <= 0 {
		height = 24
	}
	modalWidth := 58
	if width < modalWidth+4 {
		modalWidth = width - 4
		if modalWidth < 40 {
			modalWidth = 40
		}
	}

	prefix := "/"
	if m.direction == SearchBackward {
		prefix = "?"
	}
	lines := []string{
		"Regex Search",
		"",
		"Pattern:",
		prefix + m.textInput.View(),
	}
	if m.err != "" {
		lines = append(lines, "Error: "+m.err)
	}
	lines = append(lines, "", "Enter search • Esc cancel")

	box := lipgloss.NewStyle().
		Border(lipgloss.RoundedBorder()).
		Padding(1, 2).
		Width(modalWidth).
		Render(strings.Join(lines, "\n"))
	return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center, box)
}