1package tea
2
3import (
4 "context"
5 "io"
6 "sync/atomic"
7
8 "github.com/charmbracelet/colorprofile"
9 "github.com/charmbracelet/x/ansi"
10)
11
12// ProgramOption is used to set options when initializing a Program. Program can
13// accept a variable number of options.
14//
15// Example usage:
16//
17// p := NewProgram(model, WithInput(someInput), WithOutput(someOutput))
18type ProgramOption func(*Program)
19
20// WithContext lets you specify a context in which to run the Program. This is
21// useful if you want to cancel the execution from outside. When a Program gets
22// cancelled it will exit with an error ErrProgramKilled.
23func WithContext(ctx context.Context) ProgramOption {
24 return func(p *Program) {
25 p.externalCtx = ctx
26 }
27}
28
29// WithOutput sets the output which, by default, is stdout. In most cases you
30// won't need to use this.
31func WithOutput(output io.Writer) ProgramOption {
32 return func(p *Program) {
33 p.output = output
34 }
35}
36
37// WithInput sets the input which, by default, is stdin. In most cases you
38// won't need to use this. To disable input entirely pass nil.
39//
40// p := NewProgram(model, WithInput(nil))
41func WithInput(input io.Reader) ProgramOption {
42 return func(p *Program) {
43 p.input = input
44 p.inputType = customInput
45 }
46}
47
48// WithInputTTY opens a new TTY for input (or console input device on Windows).
49func WithInputTTY() ProgramOption {
50 return func(p *Program) {
51 p.inputType = ttyInput
52 }
53}
54
55// WithEnvironment sets the environment variables that the program will use.
56// This useful when the program is running in a remote session (e.g. SSH) and
57// you want to pass the environment variables from the remote session to the
58// program.
59//
60// Example:
61//
62// var sess ssh.Session // ssh.Session is a type from the github.com/charmbracelet/ssh package
63// pty, _, _ := sess.Pty()
64// environ := append(sess.Environ(), "TERM="+pty.Term)
65// p := tea.NewProgram(model, tea.WithEnvironment(environ)
66func WithEnvironment(env []string) ProgramOption {
67 return func(p *Program) {
68 p.environ = env
69 }
70}
71
72// WithoutSignalHandler disables the signal handler that Bubble Tea sets up for
73// Programs. This is useful if you want to handle signals yourself.
74func WithoutSignalHandler() ProgramOption {
75 return func(p *Program) {
76 p.startupOptions |= withoutSignalHandler
77 }
78}
79
80// WithoutCatchPanics disables the panic catching that Bubble Tea does by
81// default. If panic catching is disabled the terminal will be in a fairly
82// unusable state after a panic because Bubble Tea will not perform its usual
83// cleanup on exit.
84func WithoutCatchPanics() ProgramOption {
85 return func(p *Program) {
86 p.startupOptions |= withoutCatchPanics
87 }
88}
89
90// WithoutSignals will ignore OS signals.
91// This is mainly useful for testing.
92func WithoutSignals() ProgramOption {
93 return func(p *Program) {
94 atomic.StoreUint32(&p.ignoreSignals, 1)
95 }
96}
97
98// WithAltScreen starts the program with the alternate screen buffer enabled
99// (i.e. the program starts in full window mode). Note that the altscreen will
100// be automatically exited when the program quits.
101//
102// Example:
103//
104// p := tea.NewProgram(Model{}, tea.WithAltScreen())
105// if _, err := p.Run(); err != nil {
106// fmt.Println("Error running program:", err)
107// os.Exit(1)
108// }
109//
110// To enter the altscreen once the program has already started running use the
111// EnterAltScreen command.
112func WithAltScreen() ProgramOption {
113 return func(p *Program) {
114 p.startupOptions |= withAltScreen
115 }
116}
117
118// WithoutBracketedPaste starts the program with bracketed paste disabled.
119func WithoutBracketedPaste() ProgramOption {
120 return func(p *Program) {
121 p.startupOptions |= withoutBracketedPaste
122 }
123}
124
125// WithMouseCellMotion starts the program with the mouse enabled in "cell
126// motion" mode.
127//
128// Cell motion mode enables mouse click, release, and wheel events. Mouse
129// movement events are also captured if a mouse button is pressed (i.e., drag
130// events). Cell motion mode is better supported than all motion mode.
131//
132// This will try to enable the mouse in extended mode (SGR), if that is not
133// supported by the terminal it will fall back to normal mode (X10).
134//
135// To enable mouse cell motion once the program has already started running use
136// the EnableMouseCellMotion command. To disable the mouse when the program is
137// running use the DisableMouse command.
138//
139// The mouse will be automatically disabled when the program exits.
140func WithMouseCellMotion() ProgramOption {
141 return func(p *Program) {
142 p.startupOptions |= withMouseCellMotion // set
143 p.startupOptions &^= withMouseAllMotion // clear
144 }
145}
146
147// WithMouseAllMotion starts the program with the mouse enabled in "all motion"
148// mode.
149//
150// EnableMouseAllMotion is a special command that enables mouse click, release,
151// wheel, and motion events, which are delivered regardless of whether a mouse
152// button is pressed, effectively enabling support for hover interactions.
153//
154// This will try to enable the mouse in extended mode (SGR), if that is not
155// supported by the terminal it will fall back to normal mode (X10).
156//
157// Many modern terminals support this, but not all. If in doubt, use
158// EnableMouseCellMotion instead.
159//
160// To enable the mouse once the program has already started running use the
161// EnableMouseAllMotion command. To disable the mouse when the program is
162// running use the DisableMouse command.
163//
164// The mouse will be automatically disabled when the program exits.
165func WithMouseAllMotion() ProgramOption {
166 return func(p *Program) {
167 p.startupOptions |= withMouseAllMotion // set
168 p.startupOptions &^= withMouseCellMotion // clear
169 }
170}
171
172// WithFilter supplies an event filter that will be invoked before Bubble Tea
173// processes a tea.Msg. The event filter can return any tea.Msg which will then
174// get handled by Bubble Tea instead of the original event. If the event filter
175// returns nil, the event will be ignored and Bubble Tea will not process it.
176//
177// As an example, this could be used to prevent a program from shutting down if
178// there are unsaved changes.
179//
180// Example:
181//
182// func filter(m tea.Model, msg tea.Msg) tea.Msg {
183// if _, ok := msg.(tea.QuitMsg); !ok {
184// return msg
185// }
186//
187// model := m.(myModel)
188// if model.hasChanges {
189// return nil
190// }
191//
192// return msg
193// }
194//
195// p := tea.NewProgram(Model{}, tea.WithFilter(filter));
196//
197// if _,err := p.Run(); err != nil {
198// fmt.Println("Error running program:", err)
199// os.Exit(1)
200// }
201func WithFilter(filter func(Model, Msg) Msg) ProgramOption {
202 return func(p *Program) {
203 p.filter = filter
204 }
205}
206
207// WithFPS sets a custom maximum FPS at which the renderer should run. If
208// less than 1, the default value of 60 will be used. If over 120, the FPS
209// will be capped at 120.
210func WithFPS(fps int) ProgramOption {
211 return func(p *Program) {
212 p.fps = fps
213 }
214}
215
216// WithReportFocus enables reporting when the terminal gains and loses
217// focus. When this is enabled [FocusMsg] and [BlurMsg] messages will be sent
218// to your Update method.
219//
220// Note that while most terminals and multiplexers support focus reporting,
221// some do not. Also note that tmux needs to be configured to report focus
222// events.
223func WithReportFocus() ProgramOption {
224 return func(p *Program) {
225 p.startupOptions |= withReportFocus
226 }
227}
228
229// WithKeyReleases enables support for reporting key release events. This is
230// useful for terminals that support the Kitty keyboard protocol "Report event
231// types" progressive enhancement feature.
232//
233// Note that not all terminals support this feature. If the terminal does not
234// support this feature, the program will not receive key release events.
235func WithKeyReleases() ProgramOption {
236 return func(p *Program) {
237 p.requestedEnhancements.kittyFlags |= ansi.KittyReportEventTypes
238 p.requestedEnhancements.keyReleases = true
239 }
240}
241
242// WithUniformKeyLayout enables support for reporting key events as though they
243// were on a PC-101 layout. This is useful for uniform key event reporting
244// across different keyboard layouts. This is equivalent to the Kitty keyboard
245// protocol "Report alternate keys" and "Report all keys as escape codes"
246// progressive enhancement features.
247//
248// Note that not all terminals support this feature. If the terminal does not
249// support this feature, the program will not receive key events in
250// uniform layout format.
251func WithUniformKeyLayout() ProgramOption {
252 return func(p *Program) {
253 p.requestedEnhancements.kittyFlags |= ansi.KittyReportAlternateKeys | ansi.KittyReportAllKeysAsEscapeCodes
254 }
255}
256
257// WithGraphemeClustering disables grapheme clustering. This is useful if you
258// want to disable grapheme clustering for your program.
259//
260// Grapheme clustering is a character width calculation method that accurately
261// calculates the width of wide characters in a terminal. This is useful for
262// properly rendering double width characters such as emojis and CJK
263// characters.
264//
265// See https://mitchellh.com/writing/grapheme-clusters-in-terminals
266func WithGraphemeClustering() ProgramOption {
267 return func(p *Program) {
268 p.startupOptions |= withGraphemeClustering
269 }
270}
271
272// WithColorProfile sets the color profile that the program will use. This is
273// useful when you want to force a specific color profile. By default, Bubble
274// Tea will try to detect the terminal's color profile from environment
275// variables and terminfo capabilities. Use [tea.WithEnvironment] to set custom
276// environment variables.
277func WithColorProfile(profile colorprofile.Profile) ProgramOption {
278 return func(p *Program) {
279 p.startupOptions |= withColorProfile
280 p.profile = profile
281 }
282}
283
284// WithWindowSize sets the initial size of the terminal window. This is useful
285// when you need to set the initial size of the terminal window, for example
286// during testing or when you want to run your program in a non-interactive
287// environment.
288func WithWindowSize(width, height int) ProgramOption {
289 return func(p *Program) {
290 p.width = width
291 p.height = height
292 }
293}
294
295// WithoutKeyEnhancements disables all key enhancements. This is useful if you
296// want to disable all key enhancements for your program and keep your program
297// legacy compatible with older terminals.
298func WithoutKeyEnhancements() ProgramOption {
299 return func(p *Program) {
300 p.startupOptions |= withoutKeyEnhancements
301 }
302}