-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
280 lines (220 loc) · 6.62 KB
/
main.go
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package main
import (
"fmt"
"log"
"net/mail"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
func main() {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}
// Model is the main Model for the program
// it contains a slice of text inputs, the index of the currently focused input,
type model struct {
inputs []textinput.Model
focused int
err error
}
type (
errMsg error
)
// we'll use these constants to keep track of which input we're focused on
// and to make it easier to update the model
const (
to = iota // iota is a special golang constant that starts at 0 and increments by 1 for each const it's used in
from
subject
body
)
const (
hotPink = lipgloss.Color("#FF0687") // a nice hot pink
darkGrey = lipgloss.Color("#767676") // a dark grey
)
// we'll use these styles to render the inputs and the continue prompt
var (
inputStyle = lipgloss.NewStyle().Foreground(hotPink)
continueStyle = lipgloss.NewStyle().Foreground(darkGrey)
)
// validateAddress validates the email address
func (m model) validateAddress() error {
var err error
c := m.inputs[m.focused]
if _, err = mail.ParseAddress(c.Value()); err != nil {
err = fmt.Errorf("invalid email address")
}
return err
}
// func stringValidator(s string) error {
// if len(s) == 0 {
// return fmt.Errorf("input cannot be empty")
// }
// return nil
// }
// initialModel returns the initial model for the program
func initialModel() model {
// we'll create a slice of text inputs (for now just one)
var inputs []textinput.Model = make([]textinput.Model, 4)
inputs[to] = textinput.New()
inputs[to].Placeholder = "Enter to address here..."
inputs[to].Focus()
inputs[to].CharLimit = 50
inputs[to].Width = 50
inputs[to].Prompt = ""
// inputs[to].Validate = stringValidator
inputs[from] = textinput.New()
inputs[from].Placeholder = "Enter from address here..."
inputs[from].CharLimit = 50
inputs[from].Width = 50
inputs[from].Prompt = ""
// inputs[from].Validate = stringValidator
inputs[subject] = textinput.New()
inputs[subject].Placeholder = "Enter subject here..."
inputs[subject].CharLimit = 50
inputs[subject].Width = 50
inputs[subject].Prompt = ""
inputs[body] = textinput.New()
inputs[body].Placeholder = "Send a message..."
inputs[body].CharLimit = 50
inputs[body].Width = 50
inputs[body].Prompt = ""
return model{
inputs: inputs,
focused: 0,
err: nil,
}
}
// Init initializes the model with a command to blink the cursor
func (m model) Init() tea.Cmd {
return textinput.Blink
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// we'll need to update each of the text inputs, so we'll create a slice
var cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs))
// we'll handle the messages for each input and update them accordingly
switch msg := msg.(type) {
// KeyMsg is sent when a key is pressed while the component is in focus
case tea.KeyMsg:
// we want to handle the key presses for the inputs ourselves
switch msg.Type {
// we'll handle the enter, tab, and ctrl+n keys to focus the next input
case tea.KeyEnter, tea.KeyTab, tea.KeyCtrlN:
// we only really want to check whether the user has provided a To and From address.
// subject and body can be empty as the email can be sent without them.
if m.focused == to || m.focused == from {
m.err = m.validateAddress()
if m.err != nil {
return m, nil
}
}
m.nextInput()
// we'll handle shift+tab to focus the previous input
case tea.KeyShiftTab:
m.prevInput()
// we'll handle ctrl+s to send the message
case tea.KeyCtrlS:
// we don't want to send the message if there's an error
if m.err != nil {
return m, nil
}
m.sendMsg()
return m, tea.Quit
// we'll handle ctrl+c to quit the program
case tea.KeyCtrlC:
log.Println("Quitting...")
return m, tea.Quit
}
// we blur all the inputs so we can focus the one we want
for i := range m.inputs {
m.inputs[i].Blur()
}
// we focus the input we want
m.inputs[m.focused].Focus()
// errMsg is sent when an error is returned from a text input's Validate function
case errMsg:
// we'll set the error on the model so we can display it in the view
m.err = msg
return m, nil
}
// we loop through the inputs and update them with the message we received
for i := range m.inputs {
// we update the input and store the command it returns,
// so we can return a batch of all the commands
// we also store the updated input in the inputs slice
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
}
// we return the updated model and a batch of all the commands we received
return m, tea.Batch(cmds...)
}
// View renders the model to the screen
func (m model) View() string {
if m.err != nil {
return fmt.Sprintf(`
%s
%s
%s
%s
%s
%s
%s
%s
%s`,
// renders the to header and input
inputStyle.Width(50).Render("To:"),
m.inputs[to].View(),
// renders the from header and input
inputStyle.Width(50).Render("From:"),
m.inputs[from].View(),
// renders the subject header and input
inputStyle.Width(50).Render("Subject:"),
m.inputs[subject].View(),
// renders the body header and input
inputStyle.Width(50).Render("Body:"),
m.inputs[body].View(),
// renders the continue prompt at the bottom of the screen
continueStyle.Render("(ctrl + c to quit or ctrl + s to send) ->")) + "\n" + m.err.Error() + "\n"
}
return fmt.Sprintf(`
%s
%s
%s
%s
%s
%s
%s
%s
%s`,
// renders the to header and input
inputStyle.Width(50).Render("To:"),
m.inputs[to].View(),
// renders the from header and input
inputStyle.Width(50).Render("From:"),
m.inputs[from].View(),
// renders the subject header and input
inputStyle.Width(50).Render("Subject:"),
m.inputs[subject].View(),
// renders the body header and input
inputStyle.Width(50).Render("Body:"),
m.inputs[body].View(),
// renders the continue prompt at the bottom of the screen
continueStyle.Render("(ctrl + c to quit or ctrl + s to send) ->")) + "\n"
}
// nextInput focuses on the next input
func (m *model) nextInput() {
// we want to focus on the next input by incrementing the focused index
// and wrapping around to the beginning if we're at the end
m.focused = (m.focused + 1) % len(m.inputs)
}
// prevInput focuses on the previous input
func (m *model) prevInput() {
// we want to focus on the previous input by decrementing the focused index
// and wrapping around to the end if we're at the beginning
m.focused = (m.focused - 1 + len(m.inputs)) % len(m.inputs)
}
func (m *model) sendMsg() {
log.Println("Sending message...")
}