termio

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 26, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package termio provides interruptible I/O primitives and terminal handling.

It solves common issues with blocking I/O in Go CLI tools, particularly on Windows, where a blocked read from stdin can prevent signal delivery or cause hangs.

Key features:

  • InterruptibleReader: A reader that respects context cancellation.
  • Open: Platform-safe terminal opening (uses CONIN$ on Windows).
  • Upgrade: Automatic detection and upgrade of readers to terminal-aware handles.

InterruptibleReader is cancellation-aware; the underlying Read() call can still block.

Safety

The InterruptibleReader uses a "Shielded Return" strategy. If data arrives exactly as the context is cancelled, the reader prioritizes returning the data (Data First, Error Second). The *next* read will then check for cancellation. This ensures no data loss occurs when reading from pipes or streams.

For interactive prompts (e.g., "Confirm Delete y/N"), use InterruptibleReader.ReadInteractive. This method enforces "Error First" logic, discarding potential race-condition input to prioritize safety.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrInterrupted = errors.New("interrupted")

Functions

func IsInterrupted

func IsInterrupted(err error) bool

IsInterrupted checks if the error is related to an interruption (Context Canceled, ErrInterrupted, or EOF).

func IsTerminal

func IsTerminal(f *os.File) bool

IsTerminal checks if the given file is a terminal. This is used to decide whether to apply terminal enhancements like raw mode.

func Open

func Open() (io.ReadCloser, error)

Open returns the standard input reader.

func Upgrade

func Upgrade(r io.Reader) (io.Reader, error)

Upgrade checks if the provided reader is a file-based terminal. If it is, it upgrades it to a safe terminal reader (like CONIN$ on Windows) using Open(). If it is not (e.g. pipe, file, buffer), it returns the original reader.

Types

type Console added in v0.3.0

type Console struct {
	// contains filtered or unexported fields
}

Console provides platform-specific terminal mode management.

It abstracts raw mode toggling and terminal size queries across Windows (Console API) and POSIX (termios) platforms.

Acquire a Console with NewConsole. Always defer Console.Restore to return the terminal to its original state.

func NewConsole added in v0.3.0

func NewConsole(f *os.File) (*Console, error)

NewConsole wraps f as a Console. f must be a file descriptor that is connected to a terminal (e.g. os.Stdin, os.Stdout, or a PTY controller).

Returns an error if f is not a recognized terminal file descriptor or if the underlying terminal state cannot be read.

func (*Console) EnableRawMode added in v0.3.0

func (c *Console) EnableRawMode() error

EnableRawMode puts the terminal into raw mode: input is passed through character by character, echoing is disabled, and signal generation from special keys (Ctrl+C, Ctrl+Z, etc.) is suppressed.

Call Console.Restore (typically via defer) to return to the previous mode.

func (*Console) Fd added in v0.3.0

func (c *Console) Fd() uintptr

Fd returns the underlying file descriptor.

func (*Console) Restore added in v0.3.0

func (c *Console) Restore() error

Restore returns the terminal to the state it had when NewConsole was called.

func (*Console) Size added in v0.3.0

func (c *Console) Size() (width, height int, err error)

Size returns the current width and height of the terminal in columns and rows.

type InterruptibleReader

type InterruptibleReader struct {
	// contains filtered or unexported fields
}

InterruptibleReader wraps an io.Reader and checks for cancellation before and after reads. Note: The underlying Read() call may still block! This wrapper primarily ensures that if the context is cancelled *before* we read, we return immediately, and if cancelled *during* the read (and the read returns), we prioritize the cancellation error.

func NewInterruptibleReader

func NewInterruptibleReader(base io.Reader, cancel <-chan struct{}) *InterruptibleReader

NewInterruptibleReader returns a reader that checks the cancel channel.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/aretw0/procio/termio"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	// A reader that simulates continuous input
	reader := strings.NewReader("hello\nworld\n")

	// Wrap it with InterruptibleReader
	ir := termio.NewInterruptibleReader(reader, ctx.Done())

	// Read in a loop
	go func() {
		buf := make([]byte, 10)
		for {
			n, err := ir.Read(buf)
			if err != nil {
				if err == io.EOF {
					fmt.Println("EOF reached")
				} else {
					// Likely context cancellation propagated
					fmt.Printf("Read error: %v\n", err)
				}
				return
			}
			fmt.Printf("Read %d bytes\n", n)
		}
	}()

	// Process for a bit, then cancel the context
	time.Sleep(10 * time.Millisecond)
	cancel()

	// Wait for goroutine to yield
	time.Sleep(50 * time.Millisecond)

	// Since NewReader is fast, it might just hit EOF before the cancel.
	// This example demonstrates structure rather than precise timing.
}

func (*InterruptibleReader) Read

func (r *InterruptibleReader) Read(p []byte) (n int, err error)

func (*InterruptibleReader) ReadInteractive

func (r *InterruptibleReader) ReadInteractive(p []byte) (n int, err error)

ReadInteractive reads from the underlying source but enforces a "Strict Cancel" policy. Unlike Read() (which prioritizes Data over Error to prevent data loss), ReadInteractive prioritizes the Cancellation Error over Data.

If the context is cancelled while reading (or immediately after), any data read from the OS buffer is DISCARDED, and ErrInterrupted is returned.

Use this for interactive prompts (e.g. "Do you want to continue? y/N") where a User Interrupt (Ctrl+C) should always take precedence over the input "y", preventing accidental execution of dangerous actions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL