tea.go

   1// Package tea provides a framework for building rich terminal user interfaces
   2// based on the paradigms of The Elm Architecture. It's well-suited for simple
   3// and complex terminal applications, either inline, full-window, or a mix of
   4// both. It's been battle-tested in several large projects and is
   5// production-ready.
   6//
   7// A tutorial is available at https://github.com/charmbracelet/bubbletea/tree/master/tutorials
   8//
   9// Example programs can be found at https://github.com/charmbracelet/bubbletea/tree/master/examples
  10package tea
  11
  12import (
  13	"bytes"
  14	"context"
  15	"errors"
  16	"fmt"
  17	"image/color"
  18	"io"
  19	"log"
  20	"os"
  21	"os/signal"
  22	"runtime/debug"
  23	"strconv"
  24	"strings"
  25	"sync"
  26	"sync/atomic"
  27	"syscall"
  28	"time"
  29
  30	"github.com/charmbracelet/colorprofile"
  31	uv "github.com/charmbracelet/ultraviolet"
  32	"github.com/charmbracelet/x/ansi"
  33	"github.com/charmbracelet/x/term"
  34	"github.com/lucasb-eyer/go-colorful"
  35	"golang.org/x/sync/errgroup"
  36)
  37
  38// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.
  39var ErrProgramPanic = errors.New("program experienced a panic")
  40
  41// ErrProgramKilled is returned by [Program.Run] when the program gets killed.
  42var ErrProgramKilled = errors.New("program was killed")
  43
  44// ErrInterrupted is returned by [Program.Run] when the program get a SIGINT
  45// signal, or when it receives a [InterruptMsg].
  46var ErrInterrupted = errors.New("program was interrupted")
  47
  48// Msg contain data from the result of a IO operation. Msgs trigger the update
  49// function and, henceforth, the UI.
  50type Msg = uv.Event
  51
  52// Model contains the program's state as well as its core functions.
  53type Model interface {
  54	// Init is the first function that will be called. It returns an optional
  55	// initial command. To not perform an initial command return nil.
  56	Init() Cmd
  57
  58	// Update is called when a message is received. Use it to inspect messages
  59	// and, in response, update the model and/or send a command.
  60	Update(Msg) (Model, Cmd)
  61}
  62
  63// ViewModel is an optional interface that can be implemented by the main model
  64// to provide a view. If the main model does not implement a view interface,
  65// the program won't render anything.
  66type ViewModel interface {
  67	// View renders the program's UI, which is just a string. The view is
  68	// rendered after every Update.
  69	View() string
  70}
  71
  72// ViewableModel is an optional interface that can be implemented by the main
  73// model to provide a view that can be composed of multiple layers. If the
  74// main model does not implement a view interface, the program won't render
  75// anything.
  76type ViewableModel interface {
  77	// View returns a [View] that contains the layers to be rendered. The
  78	// layers are rendered based on their z-index, with the lowest z-index
  79	// rendered first and the highest z-index rendered last. If some layers
  80	// have the same z-index, they are rendered in the order they were added to
  81	// the view.
  82	// The cursor is optional, if it's nil the cursor will be hidden.
  83	View() View
  84}
  85
  86// StyledString returns a [Layer] that can be styled with ANSI escape
  87// codes. It is used to render text with different colors, styles, and other
  88// attributes on the terminal screen.
  89func StyledString(s string) *uv.StyledString {
  90	return uv.NewStyledString(s)
  91}
  92
  93// Buffer represents a terminal cell buffer that defines the current state of
  94// the terminal screen.
  95type Buffer = uv.Buffer
  96
  97// Screen represents a read writable canvas that can be used to render
  98// components on the terminal screen.
  99type Screen = uv.Screen
 100
 101// Rectangle represents a rectangular area with two points: the top left corner
 102// and the bottom right corner. It is used to define the area where components
 103// will be rendered on the terminal screen.
 104type Rectangle = uv.Rectangle
 105
 106// Layer represents a drawable component on a [Screen].
 107type Layer interface {
 108	// Draw renders the component on the given [Screen] within the specified
 109	// [Rectangle]. The component should draw itself within the bounds of the
 110	// rectangle, which is defined by the top left corner (x0, y0) and the
 111	// bottom right corner (x1, y1).
 112	Draw(s Screen, r Rectangle)
 113}
 114
 115// Hittable is an interface that can be implemented by a [Layer] to test
 116// whether a layer was hit by a mouse event.
 117type Hittable interface {
 118	// Hit tests the layer against the given position. If the position is
 119	// inside the layer, it returns the layer ID that was hit. If no
 120	// layer was hit, it returns an empty string.
 121	Hit(x, y int) string
 122}
 123
 124// NewView is a helper function to create a new [View] with the given string or
 125// [Layer].
 126func NewView(s any) View {
 127	var view View
 128	switch v := s.(type) {
 129	case string:
 130		view.Layer = StyledString(v)
 131	case fmt.Stringer:
 132		view.Layer = StyledString(v.String())
 133	case Layer:
 134		view.Layer = v
 135	default:
 136		view.Layer = StyledString(fmt.Sprintf("%v", v))
 137	}
 138	return view
 139}
 140
 141// View represents a terminal view that can be composed of multiple layers.
 142// It can also contain a cursor that will be rendered on top of the layers.
 143type View struct {
 144	Layer           Layer
 145	Cursor          *Cursor
 146	BackgroundColor color.Color
 147	ForegroundColor color.Color
 148	WindowTitle     string
 149}
 150
 151// Cursor represents a cursor on the terminal screen.
 152type Cursor struct {
 153	// Position is a [Position] that determines the cursor's position on the
 154	// screen relative to the top left corner of the frame.
 155	Position
 156
 157	// Color is a [color.Color] that determines the cursor's color.
 158	Color color.Color
 159
 160	// Shape is a [CursorShape] that determines the cursor's shape.
 161	Shape CursorShape
 162
 163	// Blink is a boolean that determines whether the cursor should blink.
 164	Blink bool
 165}
 166
 167// NewCursor returns a new cursor with the default settings and the given
 168// position.
 169func NewCursor(x, y int) *Cursor {
 170	return &Cursor{
 171		Position: Position{X: x, Y: y},
 172		Color:    nil,
 173		Shape:    CursorBlock,
 174		Blink:    true,
 175	}
 176}
 177
 178// CursorModel is an optional interface that can be implemented by the main
 179// model to provide a view that manages the cursor. If the main model does not
 180// implement a view interface, the program won't render anything.
 181type CursorModel interface {
 182	// View renders the program's UI, which is just a string. The view is
 183	// rendered after every Update. The cursor is optional, if it's nil the
 184	// cursor will be hidden.
 185	// Use [NewCursor] to quickly create a cursor for a given position with
 186	// default styles.
 187	View() (string, *Cursor)
 188}
 189
 190// Cmd is an IO operation that returns a message when it's complete. If it's
 191// nil it's considered a no-op. Use it for things like HTTP requests, timers,
 192// saving and loading from disk, and so on.
 193//
 194// Note that there's almost never a reason to use a command to send a message
 195// to another part of your program. That can almost always be done in the
 196// update function.
 197type Cmd func() Msg
 198
 199type inputType int
 200
 201const (
 202	defaultInput inputType = iota
 203	ttyInput
 204	customInput
 205)
 206
 207// String implements the stringer interface for [inputType]. It is inteded to
 208// be used in testing.
 209func (i inputType) String() string {
 210	return [...]string{
 211		"default input",
 212		"tty input",
 213		"custom input",
 214	}[i]
 215}
 216
 217// Options to customize the program during its initialization. These are
 218// generally set with ProgramOptions.
 219//
 220// The options here are treated as bits.
 221type startupOptions int16
 222
 223func (s startupOptions) has(option startupOptions) bool {
 224	return s&option != 0
 225}
 226
 227const (
 228	withAltScreen startupOptions = 1 << iota
 229	withMouseCellMotion
 230	withMouseAllMotion
 231	withoutSignalHandler
 232	// Catching panics is incredibly useful for restoring the terminal to a
 233	// usable state after a panic occurs. When this is set, Bubble Tea will
 234	// recover from panics, print the stack trace, and disable raw mode. This
 235	// feature is on by default.
 236	withoutCatchPanics
 237	withoutBracketedPaste
 238	withReportFocus
 239	withKittyKeyboard
 240	withModifyOtherKeys
 241	withWindowsInputMode
 242	withColorProfile
 243	withGraphemeClustering
 244	withoutKeyEnhancements
 245)
 246
 247// channelHandlers manages the series of channels returned by various processes.
 248// It allows us to wait for those processes to terminate before exiting the
 249// program.
 250type channelHandlers struct {
 251	handlers []chan struct{}
 252	mu       sync.RWMutex
 253}
 254
 255// Adds a channel to the list of handlers. We wait for all handlers to terminate
 256// gracefully on shutdown.
 257func (h *channelHandlers) add(ch chan struct{}) {
 258	h.mu.Lock()
 259	h.handlers = append(h.handlers, ch)
 260	h.mu.Unlock()
 261}
 262
 263// shutdown waits for all handlers to terminate.
 264func (h *channelHandlers) shutdown() {
 265	var wg sync.WaitGroup
 266
 267	h.mu.RLock()
 268	defer h.mu.RUnlock()
 269
 270	for _, ch := range h.handlers {
 271		wg.Add(1)
 272		go func(ch chan struct{}) {
 273			<-ch
 274			wg.Done()
 275		}(ch)
 276	}
 277	wg.Wait()
 278}
 279
 280// Program is a terminal user interface.
 281type Program struct {
 282	initialModel Model
 283
 284	// handlers is a list of channels that need to be waited on before the
 285	// program can exit.
 286	handlers channelHandlers
 287
 288	// Configuration options that will set as the program is initializing,
 289	// treated as bits. These options can be set via various ProgramOptions.
 290	startupOptions startupOptions
 291
 292	// startupTitle is the title that will be set on the terminal when the
 293	// program starts.
 294	startupTitle string
 295
 296	inputType inputType
 297
 298	// externalCtx is a context that was passed in via WithContext, otherwise defaulting
 299	// to ctx.Background() (in case it was not), the internal context is derived from it.
 300	externalCtx context.Context
 301
 302	// ctx is the programs's internal context for signalling internal teardown.
 303	// It is built and derived from the externalCtx in NewProgram().
 304	ctx    context.Context
 305	cancel context.CancelFunc
 306
 307	msgs         chan Msg
 308	errs         chan error
 309	finished     chan struct{}
 310	shutdownOnce sync.Once
 311
 312	profile colorprofile.Profile // the terminal color profile
 313
 314	// where to send output, this will usually be os.Stdout.
 315	output    io.Writer
 316	outputBuf bytes.Buffer // buffer used to queue commands to be sent to the output
 317
 318	// ttyOutput is null if output is not a TTY.
 319	ttyOutput           term.File
 320	previousOutputState *term.State
 321	renderer            renderer
 322
 323	// the environment variables for the program, defaults to os.Environ().
 324	environ uv.Environ
 325	// the program's logger for debugging.
 326	logger uv.Logger
 327
 328	// where to read inputs from, this will usually be os.Stdin.
 329	input io.Reader
 330	mu    sync.Mutex
 331	// ttyInput is null if input is not a TTY.
 332	ttyInput              term.File
 333	previousTtyInputState *term.State
 334	inputReader           *uv.TerminalReader
 335	traceInput            bool // true if input should be traced
 336	readLoopDone          chan struct{}
 337	mouseMode             bool // indicates whether we should enable mouse on Windows
 338
 339	// modes keeps track of terminal modes that have been enabled or disabled.
 340	modes         ansi.Modes
 341	ignoreSignals uint32
 342
 343	filter func(Model, Msg) Msg
 344
 345	// fps is the frames per second we should set on the renderer, if
 346	// applicable,
 347	fps int
 348
 349	// ticker is the ticker that will be used to write to the renderer.
 350	ticker *time.Ticker
 351
 352	// once is used to stop the renderer.
 353	once sync.Once
 354
 355	// rendererDone is used to stop the renderer.
 356	rendererDone chan struct{}
 357
 358	// stores the requested keyboard enhancements.
 359	requestedEnhancements KeyboardEnhancements
 360	// activeEnhancements stores the active keyboard enhancements read from the
 361	// terminal.
 362	activeEnhancements KeyboardEnhancements
 363
 364	// When a program is suspended, the terminal state is saved and the program
 365	// is paused. This saves the terminal colors state so they can be restored
 366	// when the program is resumed.
 367	setBg, setFg, setCc                       color.Color
 368	lastBgColor, lastFgColor, lastCursorColor color.Color
 369	lastWindowTitle                           string
 370
 371	// Initial window size. Mainly used for testing.
 372	width, height int
 373
 374	// whether to use hard tabs to optimize cursor movements
 375	useHardTabs bool
 376	// whether to use backspace to optimize cursor movements
 377	useBackspace bool
 378}
 379
 380// Quit is a special command that tells the Bubble Tea program to exit.
 381func Quit() Msg {
 382	return QuitMsg{}
 383}
 384
 385// QuitMsg signals that the program should quit. You can send a [QuitMsg] with
 386// [Quit].
 387type QuitMsg struct{}
 388
 389// Suspend is a special command that tells the Bubble Tea program to suspend.
 390func Suspend() Msg {
 391	return SuspendMsg{}
 392}
 393
 394// SuspendMsg signals the program should suspend.
 395// This usually happens when ctrl+z is pressed on common programs, but since
 396// bubbletea puts the terminal in raw mode, we need to handle it in a
 397// per-program basis.
 398//
 399// You can send this message with [Suspend()].
 400type SuspendMsg struct{}
 401
 402// ResumeMsg can be listen to to do something once a program is resumed back
 403// from a suspend state.
 404type ResumeMsg struct{}
 405
 406// InterruptMsg signals the program should suspend.
 407// This usually happens when ctrl+c is pressed on common programs, but since
 408// bubbletea puts the terminal in raw mode, we need to handle it in a
 409// per-program basis.
 410//
 411// You can send this message with [Interrupt()].
 412type InterruptMsg struct{}
 413
 414// Interrupt is a special command that tells the Bubble Tea program to
 415// interrupt.
 416func Interrupt() Msg {
 417	return InterruptMsg{}
 418}
 419
 420// NewProgram creates a new Program.
 421func NewProgram(model Model, opts ...ProgramOption) *Program {
 422	p := &Program{
 423		initialModel: model,
 424		msgs:         make(chan Msg),
 425		rendererDone: make(chan struct{}),
 426		modes:        ansi.Modes{},
 427	}
 428
 429	// Apply all options to the program.
 430	for _, opt := range opts {
 431		opt(p)
 432	}
 433
 434	// A context can be provided with a ProgramOption, but if none was provided
 435	// we'll use the default background context.
 436	if p.externalCtx == nil {
 437		p.externalCtx = context.Background()
 438	}
 439	// Initialize context and teardown channel.
 440	p.ctx, p.cancel = context.WithCancel(p.externalCtx)
 441
 442	// if no output was set, set it to stdout
 443	if p.output == nil {
 444		p.output = os.Stdout
 445	}
 446
 447	// if no environment was set, set it to os.Environ()
 448	if p.environ == nil {
 449		p.environ = os.Environ()
 450	}
 451
 452	if p.fps < 1 {
 453		p.fps = defaultFPS
 454	} else if p.fps > maxFPS {
 455		p.fps = maxFPS
 456	}
 457
 458	tracePath, traceOk := os.LookupEnv("TEA_TRACE")
 459	if traceOk && len(tracePath) > 0 {
 460		// We have a trace filepath.
 461		if f, err := os.OpenFile(tracePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666); err == nil {
 462			p.logger = log.New(f, "bubbletea: ", log.LstdFlags|log.Lshortfile)
 463		}
 464	}
 465
 466	return p
 467}
 468
 469func (p *Program) handleSignals() chan struct{} {
 470	ch := make(chan struct{})
 471
 472	// Listen for SIGINT and SIGTERM.
 473	//
 474	// In most cases ^C will not send an interrupt because the terminal will be
 475	// in raw mode and ^C will be captured as a keystroke and sent along to
 476	// Program.Update as a KeyMsg. When input is not a TTY, however, ^C will be
 477	// caught here.
 478	//
 479	// SIGTERM is sent by unix utilities (like kill) to terminate a process.
 480	go func() {
 481		sig := make(chan os.Signal, 1)
 482		signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
 483		defer func() {
 484			signal.Stop(sig)
 485			close(ch)
 486		}()
 487
 488		for {
 489			select {
 490			case <-p.ctx.Done():
 491				return
 492
 493			case s := <-sig:
 494				if atomic.LoadUint32(&p.ignoreSignals) == 0 {
 495					switch s {
 496					case syscall.SIGINT:
 497						p.msgs <- InterruptMsg{}
 498					default:
 499						p.msgs <- QuitMsg{}
 500					}
 501					return
 502				}
 503			}
 504		}
 505	}()
 506
 507	return ch
 508}
 509
 510// handleResize handles terminal resize events.
 511func (p *Program) handleResize() chan struct{} {
 512	ch := make(chan struct{})
 513
 514	if p.ttyOutput != nil {
 515		// Listen for window resizes.
 516		go p.listenForResize(ch)
 517	} else {
 518		close(ch)
 519	}
 520
 521	return ch
 522}
 523
 524// handleCommands runs commands in a goroutine and sends the result to the
 525// program's message channel.
 526func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
 527	ch := make(chan struct{})
 528
 529	go func() {
 530		defer close(ch)
 531
 532		for {
 533			select {
 534			case <-p.ctx.Done():
 535				return
 536
 537			case cmd := <-cmds:
 538				if cmd == nil {
 539					continue
 540				}
 541
 542				// Don't wait on these goroutines, otherwise the shutdown
 543				// latency would get too large as a Cmd can run for some time
 544				// (e.g. tick commands that sleep for half a second). It's not
 545				// possible to cancel them so we'll have to leak the goroutine
 546				// until Cmd returns.
 547				go func() {
 548					// Recover from panics.
 549					if !p.startupOptions.has(withoutCatchPanics) {
 550						defer func() {
 551							if r := recover(); r != nil {
 552								p.recoverFromPanic(r)
 553							}
 554						}()
 555					}
 556
 557					msg := cmd() // this can be long.
 558					p.Send(msg)
 559				}()
 560			}
 561		}
 562	}()
 563
 564	return ch
 565}
 566
 567// eventLoop is the central message loop. It receives and handles the default
 568// Bubble Tea messages, update the model and triggers redraws.
 569func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
 570	for {
 571		select {
 572		case <-p.ctx.Done():
 573			return model, nil
 574
 575		case err := <-p.errs:
 576			return model, err
 577
 578		case msg := <-p.msgs:
 579			msg = p.translateInputEvent(msg)
 580
 581			// Filter messages.
 582			if p.filter != nil {
 583				msg = p.filter(model, msg)
 584			}
 585			if msg == nil {
 586				continue
 587			}
 588
 589			// Handle special internal messages.
 590			switch msg := msg.(type) {
 591			case QuitMsg:
 592				return model, nil
 593
 594			case InterruptMsg:
 595				return model, ErrInterrupted
 596
 597			case SuspendMsg:
 598				if suspendSupported {
 599					p.suspend()
 600				}
 601
 602			case CapabilityMsg:
 603				switch msg {
 604				case "RGB", "Tc":
 605					if p.profile != colorprofile.TrueColor {
 606						p.profile = colorprofile.TrueColor
 607						go p.Send(ColorProfileMsg{p.profile})
 608					}
 609				}
 610
 611			case MouseMsg:
 612				for _, m := range p.renderer.hit(msg) {
 613					go p.Send(m) // send hit messages
 614				}
 615
 616			case modeReportMsg:
 617				switch msg.Mode {
 618				case ansi.GraphemeClusteringMode:
 619					// 1 means mode is set (see DECRPM).
 620					p.modes[ansi.GraphemeClusteringMode] = msg.Value
 621				}
 622
 623			case enableModeMsg:
 624				mode := p.modes.Get(msg.Mode)
 625				if mode.IsSet() {
 626					break
 627				}
 628
 629				p.modes.Set(msg.Mode)
 630
 631				switch msg.Mode {
 632				case ansi.AltScreenSaveCursorMode:
 633					p.renderer.enterAltScreen()
 634				case ansi.TextCursorEnableMode:
 635					p.renderer.showCursor()
 636				case ansi.GraphemeClusteringMode:
 637					// We store the state of grapheme clustering after we enable it
 638					// and get a response in the eventLoop.
 639					p.execute(ansi.SetGraphemeClusteringMode + ansi.RequestGraphemeClusteringMode)
 640				default:
 641					p.execute(ansi.SetMode(msg.Mode))
 642				}
 643
 644			case disableModeMsg:
 645				mode := p.modes.Get(msg.Mode)
 646				if mode.IsReset() {
 647					break
 648				}
 649
 650				p.modes.Reset(msg.Mode)
 651
 652				switch msg.Mode {
 653				case ansi.AltScreenSaveCursorMode:
 654					p.renderer.exitAltScreen()
 655				case ansi.TextCursorEnableMode:
 656					p.renderer.hideCursor()
 657				default:
 658					p.execute(ansi.ResetMode(msg.Mode))
 659				}
 660
 661			case enableMouseCellMotionMsg:
 662				p.enableMouse(false)
 663
 664			case enableMouseAllMotionMsg:
 665				p.enableMouse(true)
 666
 667			case disableMouseMotionMsg:
 668				p.disableMouse()
 669
 670			case readClipboardMsg:
 671				p.execute(ansi.RequestSystemClipboard)
 672
 673			case setClipboardMsg:
 674				p.execute(ansi.SetSystemClipboard(string(msg)))
 675
 676			case readPrimaryClipboardMsg:
 677				p.execute(ansi.RequestPrimaryClipboard)
 678
 679			case setPrimaryClipboardMsg:
 680				p.execute(ansi.SetPrimaryClipboard(string(msg)))
 681
 682			case setBackgroundColorMsg:
 683				// The renderer handles flushing the color to the terminal.
 684				p.lastBgColor = msg.Color
 685
 686			case setForegroundColorMsg:
 687				// The renderer handles flushing the color to the terminal.
 688				p.lastFgColor = msg.Color
 689
 690			case setCursorColorMsg:
 691				// The renderer handles flushing the color to the terminal.
 692				p.lastCursorColor = msg.Color
 693
 694			case backgroundColorMsg:
 695				p.execute(ansi.RequestBackgroundColor)
 696
 697			case foregroundColorMsg:
 698				p.execute(ansi.RequestForegroundColor)
 699
 700			case cursorColorMsg:
 701				p.execute(ansi.RequestCursorColor)
 702
 703			case KeyboardEnhancementsMsg:
 704				p.activeEnhancements.kittyFlags = msg.kittyFlags
 705				p.activeEnhancements.modifyOtherKeys = msg.modifyOtherKeys
 706
 707			case enableKeyboardEnhancementsMsg:
 708				if p.startupOptions.has(withoutKeyEnhancements) {
 709					break
 710				}
 711
 712				if isWindows() {
 713					// We use the Windows Console API which supports keyboard
 714					// enhancements.
 715					// Send an empty message to tell the user we support
 716					// keyboard enhancements on Windows.
 717					go p.Send(KeyboardEnhancementsMsg{})
 718					break
 719				}
 720
 721				var ke KeyboardEnhancements
 722				for _, e := range msg {
 723					e(&ke)
 724				}
 725
 726				p.requestedEnhancements.kittyFlags |= ke.kittyFlags
 727				if ke.modifyOtherKeys > p.requestedEnhancements.modifyOtherKeys {
 728					p.requestedEnhancements.modifyOtherKeys = ke.modifyOtherKeys
 729				}
 730
 731				p.requestKeyboardEnhancements()
 732
 733			case disableKeyboardEnhancementsMsg:
 734				if p.startupOptions.has(withoutKeyEnhancements) {
 735					break
 736				}
 737
 738				if isWindows() {
 739					// We use the Windows Console API which supports keyboard
 740					// enhancements.
 741					break
 742				}
 743
 744				if p.activeEnhancements.modifyOtherKeys > 0 {
 745					p.execute(ansi.ResetModifyOtherKeys)
 746					p.activeEnhancements.modifyOtherKeys = 0
 747					p.requestedEnhancements.modifyOtherKeys = 0
 748				}
 749				if p.activeEnhancements.kittyFlags > 0 {
 750					p.execute(ansi.DisableKittyKeyboard)
 751					p.activeEnhancements.kittyFlags = 0
 752					p.requestedEnhancements.kittyFlags = 0
 753				}
 754
 755			case execMsg:
 756				// NB: this blocks.
 757				p.exec(msg.cmd, msg.fn)
 758
 759			case terminalVersion:
 760				p.execute(ansi.RequestNameVersion)
 761
 762			case requestCapabilityMsg:
 763				p.execute(ansi.RequestTermcap(string(msg)))
 764
 765			case BatchMsg:
 766				for _, cmd := range msg {
 767					select {
 768					case <-p.ctx.Done():
 769						return model, nil
 770					case cmds <- cmd:
 771					}
 772				}
 773				continue
 774
 775			case sequenceMsg:
 776				go func() {
 777					// Execute commands one at a time, in order.
 778					for _, cmd := range msg {
 779						if cmd == nil {
 780							continue
 781						}
 782
 783						switch msg := cmd().(type) {
 784						case BatchMsg:
 785							g, _ := errgroup.WithContext(p.ctx)
 786							for _, cmd := range msg {
 787								cmd := cmd
 788								g.Go(func() error {
 789									p.Send(cmd())
 790									return nil
 791								})
 792							}
 793
 794							_ = g.Wait() // wait for all commands from batch msg to finish
 795							continue
 796						case sequenceMsg:
 797							for _, cmd := range msg {
 798								p.Send(cmd())
 799							}
 800						default:
 801							p.Send(msg)
 802						}
 803					}
 804				}()
 805
 806			case setWindowTitleMsg:
 807				p.renderer.setWindowTitle(p.lastWindowTitle)
 808				p.lastWindowTitle = string(msg)
 809
 810			case WindowSizeMsg:
 811				p.renderer.resize(msg.Width, msg.Height)
 812
 813			case windowSizeMsg:
 814				go p.checkResize()
 815
 816			case requestCursorPosMsg:
 817				p.execute(ansi.RequestCursorPositionReport)
 818
 819			case RawMsg:
 820				p.execute(fmt.Sprint(msg.Msg))
 821
 822			case printLineMessage:
 823				p.renderer.insertAbove(msg.messageBody)
 824
 825			case repaintMsg:
 826				p.renderer.repaint()
 827
 828			case clearScreenMsg:
 829				p.renderer.clearScreen()
 830
 831			case ColorProfileMsg:
 832				p.renderer.setColorProfile(msg.Profile)
 833			}
 834
 835			var cmd Cmd
 836			model, cmd = model.Update(msg) // run update
 837
 838			select {
 839			case <-p.ctx.Done():
 840				return model, nil
 841			case cmds <- cmd: // process command (if any)
 842			}
 843
 844			p.render(model) // render view
 845		}
 846	}
 847}
 848
 849// hasView returns true if the model has a view.
 850func hasView(model Model) (ok bool) {
 851	switch model.(type) {
 852	case ViewModel, CursorModel, ViewableModel:
 853		ok = true
 854	}
 855	return
 856}
 857
 858// render renders the given view to the renderer.
 859func (p *Program) render(model Model) {
 860	var view View
 861	switch model := model.(type) {
 862	case ViewModel, CursorModel:
 863		var frame string
 864		switch model := model.(type) {
 865		case ViewModel:
 866			frame = model.View()
 867		case CursorModel:
 868			frame, view.Cursor = model.View()
 869		}
 870		view.Layer = StyledString(frame)
 871		view.BackgroundColor = p.lastBgColor
 872		view.ForegroundColor = p.lastFgColor
 873		view.WindowTitle = p.lastWindowTitle
 874		if view.Cursor != nil && p.lastCursorColor != nil {
 875			view.Cursor.Color = p.lastCursorColor
 876		}
 877	case ViewableModel:
 878		view = model.View()
 879	}
 880	if p.renderer != nil {
 881		p.renderer.render(view) // send view to renderer
 882	}
 883}
 884
 885// Run initializes the program and runs its event loops, blocking until it gets
 886// terminated by either [Program.Quit], [Program.Kill], or its signal handler.
 887// Returns the final model.
 888func (p *Program) Run() (returnModel Model, returnErr error) {
 889	p.handlers = channelHandlers{}
 890	cmds := make(chan Cmd)
 891	p.errs = make(chan error, 1)
 892
 893	p.finished = make(chan struct{})
 894	defer func() {
 895		close(p.finished)
 896	}()
 897
 898	defer p.cancel()
 899
 900	switch p.inputType {
 901	case defaultInput:
 902		p.input = os.Stdin
 903
 904		// The user has not set a custom input, so we need to check whether or
 905		// not standard input is a terminal. If it's not, we open a new TTY for
 906		// input. This will allow things to "just work" in cases where data was
 907		// piped in or redirected to the application.
 908		//
 909		// To disable input entirely pass nil to the [WithInput] program option.
 910		f, isFile := p.input.(term.File)
 911		if !isFile {
 912			break
 913		}
 914		if term.IsTerminal(f.Fd()) {
 915			break
 916		}
 917
 918		f, err := openInputTTY()
 919		if err != nil {
 920			return p.initialModel, err
 921		}
 922		defer f.Close() //nolint:errcheck
 923		p.input = f
 924
 925	case ttyInput:
 926		// Open a new TTY, by request
 927		f, err := openInputTTY()
 928		if err != nil {
 929			return p.initialModel, err
 930		}
 931		defer f.Close() //nolint:errcheck
 932		p.input = f
 933
 934	case customInput:
 935		// (There is nothing extra to do.)
 936	}
 937
 938	// Handle signals.
 939	if !p.startupOptions.has(withoutSignalHandler) {
 940		p.handlers.add(p.handleSignals())
 941	}
 942
 943	// Recover from panics.
 944	if !p.startupOptions.has(withoutCatchPanics) {
 945		defer func() {
 946			if r := recover(); r != nil {
 947				returnErr = fmt.Errorf("%w: %w", ErrProgramKilled, ErrProgramPanic)
 948				p.recoverFromPanic(r)
 949			}
 950		}()
 951	}
 952
 953	// Check if output is a TTY before entering raw mode, hiding the cursor and
 954	// so on.
 955	if err := p.initTerminal(); err != nil {
 956		return p.initialModel, err
 957	}
 958
 959	// Get the initial window size.
 960	resizeMsg := WindowSizeMsg{Width: p.width, Height: p.height}
 961	if p.ttyOutput != nil {
 962		// Set the initial size of the terminal.
 963		w, h, err := term.GetSize(p.ttyOutput.Fd())
 964		if err != nil {
 965			return p.initialModel, fmt.Errorf("bubbletea: error getting terminal size: %w", err)
 966		}
 967
 968		resizeMsg.Width, resizeMsg.Height = w, h
 969	}
 970
 971	if p.renderer == nil { //nolint:nestif
 972		if hasView(p.initialModel) {
 973			stdr, ok := os.LookupEnv("TEA_STANDARD_RENDERER")
 974			if has, _ := strconv.ParseBool(stdr); ok && has {
 975				p.renderer = newRenderer(p.output)
 976			} else {
 977				// If no renderer is set use the cursed one.
 978				p.renderer = newCursedRenderer(
 979					p.output,
 980					p.environ,
 981					resizeMsg.Width,
 982					resizeMsg.Height,
 983					p.useHardTabs,
 984					p.useBackspace,
 985					p.ttyInput == nil,
 986					p.logger,
 987				)
 988			}
 989		} else {
 990			// If the model has no view we don't need a renderer.
 991			p.renderer = &nilRenderer{}
 992		}
 993	}
 994
 995	// Get the color profile and send it to the program.
 996	if !p.startupOptions.has(withColorProfile) {
 997		p.profile = colorprofile.Detect(p.output, p.environ)
 998	}
 999
1000	// Set the color profile on the renderer and send it to the program.
1001	p.renderer.setColorProfile(p.profile)
1002	go p.Send(ColorProfileMsg{p.profile})
1003
1004	// Send the initial size to the program.
1005	go p.Send(resizeMsg)
1006	p.renderer.resize(resizeMsg.Width, resizeMsg.Height)
1007
1008	// Send the environment variables used by the program.
1009	go p.Send(EnvMsg(p.environ))
1010
1011	// Init the input reader and initial model.
1012	model := p.initialModel
1013	if p.input != nil {
1014		if err := p.initInputReader(false); err != nil {
1015			return model, err
1016		}
1017	}
1018
1019	// Hide the cursor before starting the renderer. This is handled by the
1020	// renderer so we don't need to write the sequence here.
1021	p.modes.Reset(ansi.TextCursorEnableMode)
1022	p.renderer.hideCursor()
1023
1024	// Honor program startup options.
1025	if p.startupTitle != "" {
1026		p.execute(ansi.SetWindowTitle(p.startupTitle))
1027	}
1028	if p.startupOptions&withAltScreen != 0 {
1029		// Enter alternate screen mode. This is handled by the renderer so we
1030		// don't need to write the sequence here.
1031		p.modes.Set(ansi.AltScreenSaveCursorMode)
1032		p.renderer.enterAltScreen()
1033	}
1034	if p.startupOptions&withoutBracketedPaste == 0 {
1035		p.execute(ansi.SetBracketedPasteMode)
1036		p.modes.Set(ansi.BracketedPasteMode)
1037	}
1038	if p.startupOptions&withGraphemeClustering != 0 {
1039		p.execute(ansi.SetGraphemeClusteringMode)
1040		p.execute(ansi.RequestGraphemeClusteringMode)
1041		// We store the state of grapheme clustering after we query it and get
1042		// a response in the eventLoop.
1043	}
1044
1045	// Enable mouse mode.
1046	cellMotion := p.startupOptions&withMouseCellMotion != 0
1047	allMotion := p.startupOptions&withMouseAllMotion != 0
1048	if cellMotion || allMotion {
1049		p.enableMouse(allMotion)
1050	}
1051
1052	if p.startupOptions&withReportFocus != 0 {
1053		p.execute(ansi.SetFocusEventMode)
1054		p.modes.Set(ansi.FocusEventMode)
1055	}
1056
1057	if !p.startupOptions.has(withoutKeyEnhancements) {
1058		if !isWindows() {
1059			// Enable unambiguous keys using whichever protocol the terminal prefer.
1060			p.requestedEnhancements.kittyFlags |= ansi.KittyDisambiguateEscapeCodes
1061			if p.requestedEnhancements.modifyOtherKeys == 0 {
1062				p.requestedEnhancements.modifyOtherKeys = 1 // mode 1
1063			}
1064			// We use the Windows Console API which supports keyboard
1065			// enhancements.
1066			p.requestKeyboardEnhancements()
1067		} else {
1068			// Send an empty message to tell the user we support
1069			// keyboard enhancements on Windows.
1070			go p.Send(KeyboardEnhancementsMsg{})
1071		}
1072	}
1073
1074	// Start the renderer.
1075	p.startRenderer()
1076
1077	// Initialize the program.
1078	initCmd := model.Init()
1079	if initCmd != nil {
1080		ch := make(chan struct{})
1081		p.handlers.add(ch)
1082
1083		go func() {
1084			defer close(ch)
1085
1086			select {
1087			case cmds <- initCmd:
1088			case <-p.ctx.Done():
1089			}
1090		}()
1091	}
1092
1093	// Render the initial view.
1094	p.render(model)
1095
1096	// Handle resize events.
1097	p.handlers.add(p.handleResize())
1098
1099	// Process commands.
1100	p.handlers.add(p.handleCommands(cmds))
1101
1102	// Run event loop, handle updates and draw.
1103	var err error
1104	model, err = p.eventLoop(model, cmds)
1105
1106	if err == nil && len(p.errs) > 0 {
1107		err = <-p.errs // Drain a leftover error in case eventLoop crashed.
1108	}
1109
1110	killed := p.externalCtx.Err() != nil || p.ctx.Err() != nil || err != nil
1111	if killed {
1112		if err == nil && p.externalCtx.Err() != nil {
1113			// Return also as context error the cancellation of an external context.
1114			// This is the context the user knows about and should be able to act on.
1115			err = fmt.Errorf("%w: %w", ErrProgramKilled, p.externalCtx.Err())
1116		} else if err == nil && p.ctx.Err() != nil {
1117			// Return only that the program was killed (not the internal mechanism).
1118			// The user does not know or need to care about the internal program context.
1119			err = ErrProgramKilled
1120		} else {
1121			// Return that the program was killed and also the error that caused it.
1122			err = fmt.Errorf("%w: %w", ErrProgramKilled, err)
1123		}
1124	} else {
1125		// Graceful shutdown of the program (not killed):
1126		// Ensure we rendered the final state of the model.
1127		p.render(model)
1128	}
1129
1130	// Restore terminal state.
1131	p.shutdown(killed)
1132
1133	return model, err
1134}
1135
1136// Send sends a message to the main update function, effectively allowing
1137// messages to be injected from outside the program for interoperability
1138// purposes.
1139//
1140// If the program hasn't started yet this will be a blocking operation.
1141// If the program has already been terminated this will be a no-op, so it's safe
1142// to send messages after the program has exited.
1143func (p *Program) Send(msg Msg) {
1144	select {
1145	case <-p.ctx.Done():
1146	case p.msgs <- msg:
1147	}
1148}
1149
1150// Quit is a convenience function for quitting Bubble Tea programs. Use it
1151// when you need to shut down a Bubble Tea program from the outside.
1152//
1153// If you wish to quit from within a Bubble Tea program use the Quit command.
1154//
1155// If the program is not running this will be a no-op, so it's safe to call
1156// if the program is unstarted or has already exited.
1157func (p *Program) Quit() {
1158	p.Send(Quit())
1159}
1160
1161// Kill stops the program immediately and restores the former terminal state.
1162// The final render that you would normally see when quitting will be skipped.
1163// [program.Run] returns a [ErrProgramKilled] error.
1164func (p *Program) Kill() {
1165	p.shutdown(true)
1166}
1167
1168// Wait waits/blocks until the underlying Program finished shutting down.
1169func (p *Program) Wait() {
1170	<-p.finished
1171}
1172
1173// execute writes the given sequence to the program output.
1174func (p *Program) execute(seq string) {
1175	_, _ = p.outputBuf.WriteString(seq)
1176}
1177
1178// flush flushes the output buffer to the program output.
1179func (p *Program) flush() error {
1180	if p.outputBuf.Len() == 0 {
1181		return nil
1182	}
1183	if p.logger != nil {
1184		p.logger.Printf("output: %q", p.outputBuf.String())
1185	}
1186	_, err := p.output.Write(p.outputBuf.Bytes())
1187	p.outputBuf.Reset()
1188	if err != nil {
1189		return fmt.Errorf("error writing to output: %w", err)
1190	}
1191	return nil
1192}
1193
1194// shutdown performs operations to free up resources and restore the terminal
1195// to its original state.
1196func (p *Program) shutdown(kill bool) {
1197	p.shutdownOnce.Do(func() {
1198		p.cancel()
1199
1200		// Wait for all handlers to finish.
1201		p.handlers.shutdown()
1202
1203		// Check if the cancel reader has been setup before waiting and closing.
1204		if p.inputReader != nil {
1205			// Wait for input loop to finish.
1206			if p.inputReader.Cancel() {
1207				if !kill {
1208					p.waitForReadLoop()
1209				}
1210			}
1211			_ = p.inputReader.Close()
1212		}
1213
1214		if p.renderer != nil {
1215			p.stopRenderer(kill)
1216		}
1217
1218		_ = p.restoreTerminalState()
1219	})
1220}
1221
1222// recoverFromPanic recovers from a panic, prints the stack trace, and restores
1223// the terminal to a usable state.
1224func (p *Program) recoverFromPanic(r any) {
1225	select {
1226	case p.errs <- ErrProgramPanic:
1227	default:
1228	}
1229	p.cancel() // Just in case a previous shutdown has failed.
1230	s := strings.ReplaceAll(
1231		fmt.Sprintf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r),
1232		"\n", "\r\n")
1233	fmt.Fprintln(os.Stderr, s)
1234	stack := strings.ReplaceAll(fmt.Sprintf("%s", debug.Stack()), "\n", "\r\n")
1235	fmt.Fprintln(os.Stderr, stack)
1236	p.shutdown(true)
1237}
1238
1239// ReleaseTerminal restores the original terminal state and cancels the input
1240// reader. You can return control to the Program with RestoreTerminal.
1241func (p *Program) ReleaseTerminal() error {
1242	return p.releaseTerminal(false)
1243}
1244
1245func (p *Program) releaseTerminal(reset bool) error {
1246	atomic.StoreUint32(&p.ignoreSignals, 1)
1247	if p.inputReader != nil {
1248		p.inputReader.Cancel()
1249	}
1250
1251	p.waitForReadLoop()
1252
1253	if p.renderer != nil {
1254		p.stopRenderer(false)
1255		if reset {
1256			p.renderer.reset()
1257		}
1258	}
1259
1260	return p.restoreTerminalState()
1261}
1262
1263// RestoreTerminal reinitializes the Program's input reader, restores the
1264// terminal to the former state when the program was running, and repaints.
1265// Use it to reinitialize a Program after running ReleaseTerminal.
1266func (p *Program) RestoreTerminal() error {
1267	atomic.StoreUint32(&p.ignoreSignals, 0)
1268
1269	if err := p.initTerminal(); err != nil {
1270		return err
1271	}
1272	if err := p.initInputReader(false); err != nil {
1273		return err
1274	}
1275	if p.modes.IsReset(ansi.AltScreenSaveCursorMode) {
1276		// entering alt screen already causes a repaint.
1277		go p.Send(repaintMsg{})
1278	}
1279
1280	p.startRenderer()
1281	if p.modes.IsSet(ansi.BracketedPasteMode) {
1282		p.execute(ansi.SetBracketedPasteMode)
1283	}
1284	if p.activeEnhancements.modifyOtherKeys != 0 {
1285		p.execute(ansi.KeyModifierOptions(4, p.activeEnhancements.modifyOtherKeys)) //nolint:mnd
1286	}
1287	if p.activeEnhancements.kittyFlags != 0 {
1288		p.execute(ansi.PushKittyKeyboard(p.activeEnhancements.kittyFlags))
1289	}
1290	if p.modes.IsSet(ansi.FocusEventMode) {
1291		p.execute(ansi.SetFocusEventMode)
1292	}
1293	if p.modes.IsSet(ansi.ButtonEventMouseMode) || p.modes.IsSet(ansi.AnyEventMouseMode) {
1294		if p.startupOptions&withMouseCellMotion != 0 {
1295			p.execute(ansi.SetButtonEventMouseMode)
1296			p.execute(ansi.SetSgrExtMouseMode)
1297		} else if p.startupOptions&withMouseAllMotion != 0 {
1298			p.execute(ansi.SetAnyEventMouseMode)
1299			p.execute(ansi.SetSgrExtMouseMode)
1300		}
1301	}
1302	if p.modes.IsSet(ansi.GraphemeClusteringMode) {
1303		p.execute(ansi.SetGraphemeClusteringMode)
1304	}
1305
1306	// Restore terminal colors.
1307	if p.setBg != nil {
1308		c, ok := colorful.MakeColor(p.setBg)
1309		if ok {
1310			p.execute(ansi.SetBackgroundColor(c.Hex()))
1311		}
1312	}
1313	if p.setFg != nil {
1314		c, ok := colorful.MakeColor(p.setFg)
1315		if ok {
1316			p.execute(ansi.SetForegroundColor(c.Hex()))
1317		}
1318	}
1319	if p.setCc != nil {
1320		c, ok := colorful.MakeColor(p.setCc)
1321		if ok {
1322			p.execute(ansi.SetCursorColor(c.Hex()))
1323		}
1324	}
1325
1326	// If the output is a terminal, it may have been resized while another
1327	// process was at the foreground, in which case we may not have received
1328	// SIGWINCH. Detect any size change now and propagate the new size as
1329	// needed.
1330	go p.checkResize()
1331
1332	// Flush queued commands.
1333	return p.flush()
1334}
1335
1336// Println prints above the Program. This output is unmanaged by the program
1337// and will persist across renders by the Program.
1338//
1339// If the altscreen is active no output will be printed.
1340func (p *Program) Println(args ...any) {
1341	p.msgs <- printLineMessage{
1342		messageBody: fmt.Sprint(args...),
1343	}
1344}
1345
1346// Printf prints above the Program. It takes a format template followed by
1347// values similar to fmt.Printf. This output is unmanaged by the program and
1348// will persist across renders by the Program.
1349//
1350// Unlike fmt.Printf (but similar to log.Printf) the message will be print on
1351// its own line.
1352//
1353// If the altscreen is active no output will be printed.
1354func (p *Program) Printf(template string, args ...any) {
1355	p.msgs <- printLineMessage{
1356		messageBody: fmt.Sprintf(template, args...),
1357	}
1358}
1359
1360// startRenderer starts the renderer.
1361func (p *Program) startRenderer() {
1362	framerate := time.Second / time.Duration(p.fps)
1363	if p.ticker == nil {
1364		p.ticker = time.NewTicker(framerate)
1365	} else {
1366		// If the ticker already exists, it has been stopped and we need to
1367		// reset it.
1368		p.ticker.Reset(framerate)
1369	}
1370
1371	// Since the renderer can be restarted after a stop, we need to reset
1372	// the done channel and its corresponding sync.Once.
1373	p.once = sync.Once{}
1374
1375	// Start the renderer.
1376	go func() {
1377		for {
1378			select {
1379			case <-p.rendererDone:
1380				p.ticker.Stop()
1381				return
1382
1383			case <-p.ticker.C:
1384				_ = p.flush()
1385				_ = p.renderer.flush(p)
1386			}
1387		}
1388	}()
1389}
1390
1391// stopRenderer stops the renderer.
1392// If kill is true, the renderer will be stopped immediately without flushing
1393// the last frame.
1394func (p *Program) stopRenderer(kill bool) {
1395	// Stop the renderer before acquiring the mutex to avoid a deadlock.
1396	p.once.Do(func() {
1397		p.rendererDone <- struct{}{}
1398	})
1399
1400	if !kill {
1401		// flush locks the mutex
1402		_ = p.renderer.flush(p)
1403	}
1404
1405	_ = p.renderer.close()
1406}
1407
1408// requestKeyboardEnhancements tries to enable keyboard enhancements and read
1409// the active keyboard enhancements from the terminal.
1410func (p *Program) requestKeyboardEnhancements() {
1411	if p.requestedEnhancements.modifyOtherKeys > 0 {
1412		p.execute(ansi.KeyModifierOptions(4, p.requestedEnhancements.modifyOtherKeys)) //nolint:mnd
1413		p.execute(ansi.QueryModifyOtherKeys)
1414	}
1415	if p.requestedEnhancements.kittyFlags > 0 {
1416		p.execute(ansi.PushKittyKeyboard(p.requestedEnhancements.kittyFlags))
1417		p.execute(ansi.RequestKittyKeyboard)
1418	}
1419}
1420
1421// enableMouse enables mouse events on the terminal. When all is true, it will
1422// enable [ansi.AnyEventMouseMode], otherwise, it will use
1423// [ansi.ButtonEventMouseMode].
1424// Note this has no effect on Windows since we use the Windows Console API.
1425func (p *Program) enableMouse(all bool) {
1426	if isWindows() {
1427		// XXX: This is used to enable mouse mode on Windows. We need
1428		// to reinitialize the cancel reader to get the mouse events to
1429		// work.
1430		if !p.mouseMode {
1431			p.mouseMode = true
1432			if p.inputReader != nil {
1433				// Only reinitialize if the input reader has been initialized.
1434				_ = p.initInputReader(true)
1435			}
1436		}
1437	}
1438
1439	if all {
1440		p.execute(ansi.SetAnyEventMouseMode + ansi.SetSgrExtMouseMode)
1441		p.modes.Set(ansi.AnyEventMouseMode, ansi.SgrExtMouseMode)
1442	} else {
1443		p.execute(ansi.SetButtonEventMouseMode + ansi.SetSgrExtMouseMode)
1444		p.modes.Set(ansi.ButtonEventMouseMode, ansi.SgrExtMouseMode)
1445	}
1446}
1447
1448// disableMouse disables mouse events on the terminal.
1449// Note this has no effect on Windows since we use the Windows Console API.
1450func (p *Program) disableMouse() {
1451	if isWindows() {
1452		// XXX: On Windows, mouse mode is enabled on the input reader
1453		// level. We need to instruct the input reader to stop reading
1454		// mouse events.
1455		if p.mouseMode {
1456			p.mouseMode = false
1457			if p.inputReader != nil {
1458				// Only reinitialize if the input reader has been initialized.
1459				_ = p.initInputReader(true)
1460			}
1461		}
1462	}
1463
1464	var modes []ansi.Mode
1465	if p.modes.IsSet(ansi.AnyEventMouseMode) {
1466		modes = append(modes, ansi.AnyEventMouseMode)
1467	}
1468	if p.modes.IsSet(ansi.ButtonEventMouseMode) {
1469		modes = append(modes, ansi.ButtonEventMouseMode)
1470	}
1471	if len(modes) > 0 {
1472		modes = append(modes, ansi.SgrExtMouseMode)
1473		for _, m := range modes {
1474			// We could combine all of these modes into one single sequence,
1475			// but we're being cautious here for terminals that might not support
1476			// that format i.e. `CSI ? 10003 ; 1006 l`.
1477			p.execute(ansi.ResetMode(m))
1478			p.modes.Reset(m)
1479		}
1480	}
1481}