Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions examples/counter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
tea "charm.land/bubbletea/v2"
)

// This example demonstrates using Bubbletea without visual output.
// By using WithoutOutput(), the renderer is disabled but raw mode is still
// enabled for proper keyboard input handling (arrow keys work without Enter).
// Output processing is preserved so that println and logging libraries work
// correctly without needing manual "\r\n" handling.
//
// This is useful for non-TUI applications that still need Bubbletea's
// event handling and state management but want to use standard output
// functions like println or logging libraries.
func main() {
p := tea.NewProgram(model(5), tea.WithoutOutput())
if _, err := p.Run(); err != nil {
panic(err)
}
}

type model int

func (m model) Init() tea.Cmd {
return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up":
m++
println(m)
case "down":
m--
println(m)
}
}
return m, nil
}

func (m model) View() tea.View {
return tea.NewView("")
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.18.0 // indirect
)

replace github.com/charmbracelet/x/term => ../x/term
17 changes: 17 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,23 @@ func WithoutRenderer() ProgramOption {
}
}

// WithoutOutput disables visual output from the renderer while still enabling
// raw mode for proper keyboard input handling. This is useful when you want
// keyboard events (like arrow keys) to work properly, but you want to use
// standard output (println, log libraries, etc.) without interference.
//
// Unlike WithoutRenderer, this option preserves output processing (ONLCR flag)
// so that newlines are automatically converted to carriage return + newline,
// making standard output functions work correctly.
//
// This is ideal for applications that need Bubble Tea's input handling but
// want to output using standard logging libraries or print statements.
func WithoutOutput() ProgramOption {
return func(p *Program) {
p.disableOutput = true
}
}

// WithFilter supplies an event filter that will be invoked before Bubble Tea
// processes a tea.Msg. The event filter can return any tea.Msg which will then
// get handled by Bubble Tea instead of the original event. If the event filter
Expand Down
7 changes: 6 additions & 1 deletion tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,11 @@ type Program struct {
// UI but still want to take advantage of Bubble Tea's architecture.
disableRenderer bool

// disableOutput enables raw mode for input handling but preserves output
// processing (ONLCR) so that standard output functions like println work
// correctly. The renderer is disabled in this mode.
disableOutput bool

// handlers is a list of channels that need to be waited on before the
// program can exit.
handlers channelHandlers
Expand Down Expand Up @@ -1020,7 +1025,7 @@ func (p *Program) Run() (returnModel Model, returnErr error) {
resizeMsg := WindowSizeMsg{Width: p.width, Height: p.height}

if p.renderer == nil {
if p.disableRenderer {
if p.disableRenderer || p.disableOutput {
p.renderer = &nilRenderer{}
} else {
// If no renderer is set use the cursed one.
Expand Down
2 changes: 1 addition & 1 deletion tty.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (p *Program) suspend() {
}

func (p *Program) initTerminal() error {
if p.disableRenderer {
if p.disableRenderer && !p.disableOutput {
return nil
}
return p.initInput()
Expand Down
14 changes: 12 additions & 2 deletions tty_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@ func (p *Program) initInput() (err error) {
// Check if input is a terminal
if f, ok := p.input.(term.File); ok && term.IsTerminal(f.Fd()) {
p.ttyInput = f
p.previousTtyInputState, err = term.MakeRaw(p.ttyInput.Fd())

if p.disableOutput {
// Use raw mode that preserves output processing
p.previousTtyInputState, err = term.MakeRawOutput(p.ttyInput.Fd())
} else {
// Use standard raw mode
p.previousTtyInputState, err = term.MakeRaw(p.ttyInput.Fd())
}

if err != nil {
return fmt.Errorf("error entering raw mode: %w", err)
}

// OPTIM: We can use hard tabs and backspaces to optimize cursor
// movements. This is based on termios settings support and whether
// they exist and enabled.
p.checkOptimizedMovements(p.previousTtyInputState)
if !p.disableOutput {
p.checkOptimizedMovements(p.previousTtyInputState)
}
}

if f, ok := p.output.(term.File); ok && term.IsTerminal(f.Fd()) {
Expand Down
16 changes: 12 additions & 4 deletions tty_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func (p *Program) initInput() (err error) {
}

// Save output screen buffer state and enable VT processing.
// Skip output modifications in disableOutput mode to preserve normal output behavior.
if f, ok := p.output.(term.File); ok && term.IsTerminal(f.Fd()) {
p.ttyOutput = f
p.previousOutputState, err = term.GetState(f.Fd())
Expand All @@ -45,10 +46,17 @@ func (p *Program) initInput() (err error) {
return fmt.Errorf("error getting console mode: %w", err)
}

if err := windows.SetConsoleMode(windows.Handle(p.ttyOutput.Fd()),
mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING|
windows.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
return fmt.Errorf("error setting console mode: %w", err)
if p.disableOutput {
if err := windows.SetConsoleMode(windows.Handle(p.ttyOutput.Fd()),
mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err != nil {
return fmt.Errorf("error setting console mode: %w", err)
}
} else {
if err := windows.SetConsoleMode(windows.Handle(p.ttyOutput.Fd()),
mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING|
windows.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
return fmt.Errorf("error setting console mode: %w", err)
}
}

//nolint:godox
Expand Down
Loading