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