catcurses.um

type Window and type Terminal

The main abstractions for a terminal and a window in the library.

type (
	Window* = struct { _: RawWindow
		// Reference back to the terminal owning this window.
		terminal: weak ^Terminal
	}

	Terminal* = struct { _: RawTerminal
		// The main window owned by this terminal.
		window: Window

		// The maximum amount of colors supported in this terminal.
		// Set on creation (see `fn mkTerminal`).
		maxColors: int

		// The amount of color pairs currently allocated.
		// Used internally, but can be modified.
		pairAmt: int
	}
)

fn sessionExists

Reports whether a terminal is set. (In most cases, this serves to check whether ncurses is initialized.)

fn sessionExists*(): bool

fn curTerminal

Returns the current terminal. See fn (^Terminal) makeCurrent.

fn curTerminal*(): ^Terminal

fn (^Terminal) makeCurrent

Makes t the "current" terminal; that is, the terminal whose associated window is shown on-screen. Reports whether t was successfully set as the current terminal.

fn (t: ^Terminal) makeCurrent*(): bool

fn stdTerminal

Returns the standard terminal. If ncurses hasn't been initialized yet, this function will initialize it.

fn stdTerminal*(): ^Terminal

fn mkTerminal

Creates a new terminal. If termType isn't empty, the created terminal is created with said type. If outf and inf are null, they default to std::stdout() and std::stdin(), respectively.

If no terminal existed previously, initializes ncurses and returns a stub representing the standard terminal. termType, outf and inf are ignored in this case.

fn mkTerminal*(termType: str = "", outf: std::File = null, inf: std::File = null): (^Terminal, std::Err)

type ColorPairID

Identifies a specific, preinitialized color pair.

type ColorPairID* = int

fn (^ColorPairID) string

Returns the string representation of this color pair identifier. For debugging purposes.

fn (c: ^ColorPairID) string*(): str { return sprintf("<color pair %d>", c^) }

type StandardColor

Indices to the standard terminal colors.

type StandardColor* = enum {
	black
	red
	green
	yellow
	blue
	magenta
	cyan
	white

	// "Bright" colors
	brightBlack
	brightRed
	brightGreen
	brightYellow
	brightBlue
	brightMagenta
	brightCyan
	brightWhite
}

// Aliases
const (
	gray* = StandardColor.white
	darkGray* = StandardColor.brightBlack
)

fn (^Terminal) supportsColors

Reports whether this terminal supports colors.

fn (t: ^Terminal) supportsColors*(): bool

fn (^Terminal) canChangeColors

Reports whether this terminal can change its colors.

fn (t: ^Terminal) canChangeColors*(): bool

fn (^Terminal) enableColors

Enables color capability. Returns whether it was enabled.

fn (t: ^Terminal) enableColors*(): bool

fn (^Terminal) setColorPair

Associates pair with bg and fg as background/foreground colors.

fn (t: ^Terminal) setColorPair*(pair: ColorPairID, bg, fg: StandardColor): bool

fn (t: ^Terminal) addColorPair

Allocates a new color pair, with bg and fg as background/foreground colors, and returns its ID.

fn (t: ^Terminal) addColorPair*(bg, fg: StandardColor): ColorPairID

fn (^Terminal) setColor

Changes the RGB values associated with color to r, g and b. r, g and b should be normalized floats (0-1, they will be clamped if outside that range), and will be quantized to a 0-1000 integer range for ncurses.

This function will affect every color pair using color as a palette entry.

It will do nothing (and return false) if:

  • the terminal doesn't support colors (see fn (^Terminal) supportsColors),
  • the colors are unable to be changed (see fn (^Terminal) canChangeColors), or
  • color is outside the range of colors the terminal can support (see type Terminal)
fn (t: ^Terminal) setColor*(color: StandardColor, r, g, b: real): bool

fn (^Terminal) setColorBytes

Alternate version of fn (^Terminal) setColor that takes in byte values (0-255).

fn (t: ^Terminal) setColorBytes*(color: StandardColor, r, g, b: uint8): bool

fn (^Terminal) setColorHex

Alternate version of fn (^Terminal) setColor that takes in a full hex value (0xRRGGBB).

fn (t: ^Terminal) setColorHex*(color: StandardColor, rgb: uint32): bool

fn (^Terminal) destroy

Destroys the underlying terminal. t is no longer valid for use after calling this method. If t is the standard terminal, this method will finalize ncurses.

fn (t: ^Terminal) destroy*()

fn (^Terminal) cbreak

Enables/disables cbreak mode (disables line buffering but still interprets signals) for the terminal. Must only be called on the standard terminal (see fn stdTerminal). Reports whether the terminal was set/unset to cbreak mode.

fn (t: ^Terminal) cbreak*(b: bool): bool

fn (^Terminal) echo

Enables/disables character echoing after each keypress. Must only be called on the standard terminal (see fn stdTerminal). Reports whether the echo was able to be set/unset.

fn (t: ^Terminal) echo*(b: bool): bool

fn (^Terminal) nl

Controls translation of the return key into NL (10, 0x0a, '\n'). This allows for detecting whether the return key was pressed, amongst other benefits (see man 3x nl). Must only be called on the standard terminal (see fn stdTerminal). Reports whether the translation was able to be enabled/disabled.

fn (t: ^Terminal) nl*(b: bool): bool

fn (^Terminal) raw

Enables/disables raw mode for the terminal. Must only be called on the standard terminal (see fn stdTerminal). Reports whether the terminal was set/unset to raw mode.

fn (t: ^Terminal) raw*(b: bool): bool

type Visibility

Cursor visibility specifiers. See fn (^Terminal) cursorVisibility.

type Visibility* = enum { hidden; visible; veryVisible }

fn (^Visibility) string

Returns the string representation of this visibility specifier. For debugging purposes.

fn (v: ^Visibility) string*(): str

fn (^Terminal) cursorVisibility

Changes the cursor visibility to vis (see type Visibility). Must only be called on the standard terminal (see fn stdTerminal). Returns the previous visibility and whether it was able to change it at all.

fn (t: ^Terminal) cursorVisibility*(v: Visibility): (Visibility, bool)

fn (^Terminal) escapeDelay

Changes the amount of time ncurses waits for further input to disambiguate escapes from escape sequences. Only applicable when keypad(true); otherwise, the program must disambiguate escapes itself.

fn (t: ^Terminal) escapeDelay*(n: int)

type Key

The enum wrapping the value returned by fn (^Window) getKey. If getKey returns an ASCII keypress, the value wrapped under this enum will be that key.

[!WARNING] The function keys (F0..F12 and arrow keys, amongst others) do not match the values used by ncurses, and don't currently intend to do so.

type Key* = enum {
	tab = 9
	newline = 10
	enter = 13 // '\r' in raw mode

	escape = 0x1b

	// Function keys
	f0 = 1000; f1; f2; f3; f4; f5; f6; f7; f8; f9; f10; f11; f12

	down; up; left; right // Arrow keys
	pageUp; pageDown
	home; end
	insert; delete
	backspace

	// No key was pressed; applicable when
	// `timeout` or `noDelay` have been set
	// (technical detail: returned when errno = EAGAIN)
	none
}

fn (^Key) string

Returns the string representation of this key. For debugging purposes.

fn (k: ^Key) string*(): str

fn keyF

Similar to ncurses' KEY_F(n) macro.

fn keyF*(i: int): Key { return Key(int(Key.f0)+i) }

const ctrlMask

The bitmask pertaining to CTRL+char presses.

const ctrlMask* = uint8(0x1f)

fn ctrlKey

Returns the CTRL representation of k.

fn ctrlKey*(k: Key): Key { return Key(int(k) & ctrlMask) }

fn (^Window) keypad

Attempts to enable/disable function key detection in w. Reports whether it was able to do so.

fn (w: ^Window) keypad*(enable: bool): bool

fn (^Window) noDelay

Disables delay entirely (makes fn (^Window) getKey and related non-blocking). Reports whether the operation succeeded.

fn (w: ^Window) noDelay*(b: bool): bool

Constants for fn (^Window) timeout.

const (
	blocking* = -1
	nonBlocking*
)

fn (^Window) timeout

Sets the timeout for reading operations (fn (^Window) getKey and others). Fine-grained; has millisecond-level control, as opposed to man 3x halfdelay.

If n:

  • = -1/blocking, calls to getKey and friends will wait forever.
  • = 0/nonBlocking, calls to getKey and friends will return immediately.
  • Otherwise, waits up to n milliseconds.
fn (w: ^Window) timeout*(n: int)

fn (^Window) setCursorPos

Moves the cursor to (x,y), relative to this window. Reports whether it was able to do so.

fn (w: ^Window) setCursorPos*(x, y: int): bool

fn (^Window) writeChar

Writes c at the current position of the cursor. Reports whether it was able to do so.

fn (w: ^Window) writeChar*(c: char): bool

fn (^Window) writeString

Writes n bytes of s at the current position of the cursor. If n == -1, writes the whole string. (default behavior) Reports whether it could be written. Fails if n != -1 and n > len(s).

fn (w: ^Window) writeString*(s: str, n: int = -1): bool

fn (^Window) writeStringAt

Analogous to fn (^Window) writeString, but moves the cursor to (x,y).

fn (w: ^Window) writeStringAt*(x, y: int, s: str, n: int = -1): bool

fn (^Window) print

Prints a formatted string at the current position of the cursor. Reports whether it could be written.

The format string follows fmt.um's syntax.

fn (w: ^Window) print*(fmt: str, a: ..any): bool

fn (^Window) printAt

Moves the cursor to (x,y) and prints a formatted string there. Reports whether it could be written.

The format string follows fmt.um's syntax.

fn (w: ^Window) printAt*(x, y: int, fmt: str, a: ..any): bool

fn (^Window) getKey

Returns the current keystroke, if any. Return values depend on the standard terminal's settings; check man 3x getch for information.

fn (w: ^Window) getKey*(): (Key, std::Err)

fn (^Window) refresh

Redraws this window, if applicable.

fn (w: ^Window) refresh*(): bool

type Attribute

Color-independent character attributes.

type Attribute* = enum {
	normal     // Normal display (no highlight)
	standOut   // Best highlighting mode of the terminal
	underline  // Underlining
	reverse    // Reverse video
	blink      // Blinking
	dim        // Half bright
	bold       // Extra bright or bold
	protect    // Protected mode
	invis      // Invisible or blank mode
	altCharset // Alternate character set
	italic     // Italics (non-X/Open extension)
}

fn (^Attribute) string

Returns the string representation of this character attribute. For debugging purposes.

fn (a: ^Attribute) string*(): str

fn (^Window) attrOn

Enables all the attributes listed in attrs (see type Attribute). Reports whether any of the attributes could not be enabled, and which one.

fn (w: ^Window) attrOn*(attrs: ..Attribute): (bool, Attribute)

fn (^Window) attrOff

Disables all the attributes listed in attrs (see type Attribute). Reports whether any of the attributes could not be disabled, and which one.

fn (w: ^Window) attrOff*(attrs: ..Attribute): (bool, Attribute)

fn (^Window) attrListOn

Alternate version of fn (^Window) attrOn that explicitly takes a list.

fn (w: ^Window) attrListOn*(attrs: []Attribute): (bool, Attribute)

fn (^Window) attrListOff

Alternate version of fn (^Window) attrOff that explicitly takes a list.

fn (w: ^Window) attrListOff*(attrs: []Attribute): (bool, Attribute)

fn (^Window) getAttributes

Returns the set of attributes and color pair currently applied to this window. Returns null for the attributes map on error.

fn (w: ^Window) getAttributes*(): (^map[Attribute]bool, ColorPairID)

fn (^Window) setAttributes

Overwrites the set attributes and color pair currently set for this window.

fn (w: ^Window) setAttributes*(attrs: map[Attribute]bool, pair: ColorPairID): bool

fn (^Window) useColorPair

Sets pair as the current color pair for this window's text output. Reports whether it could not be set.

fn (w: ^Window) useColorPair*(pair: ColorPairID): bool

fn (^Window) detachColorPair

Stops using pair as the current color pair for this window's text output. Reports whether it could not be disabled.

fn (w: ^Window) detachColorPair*(pair: ColorPairID): bool

type WithFn

Function type taken as argument by all fn (^Window) with* functions.

type WithFn* = fn(win: ^Window)

fn (^Window) withAttrs

Runs f with all attributes in attrs enabled, and disables them when the function ends.

fn (w: ^Window) withAttrs*(attrs: []Attribute, f: WithFn)

fn (^Window) withColorPair

Runs f with pair set as the current color pair, and unsets it when the function ends.

fn (w: ^Window) withColorPair*(pair: ColorPairID, f: WithFn)

fn (^Window) withAttrsAndColorPair

Shorthand for:

win.withAttrs(attrs, { win.withColorPair(pair, f) })
fn (w: ^Window) withAttrsAndColorPair*(attrs: []Attribute, pair: ColorPairID, f: WithFn)

fn (^Window) clear

Clears this window.

fn (w: ^Window) clear*(): bool

fn (^Window) erase

Fills this window with blank characters.

fn (w: ^Window) erase*(): bool

fn (^Window) getSize

Returns the width and height of the window, in that order.

fn (w: ^Window) getSize*(): (int, int)

fn (^Window) getWidth

Returns the width of the window. Shorthand for w.getSize().item0.

fn (w: ^Window) getWidth*(): int { return w.getSize().item0 }

fn (^Window) getHeight

Returns the height of the window. Shorthand for w.getSize().item1.

fn (w: ^Window) getHeight*(): int { return w.getSize().item1 }

fn (^Window) getCursorPos

Returns the position of the cursor relative to this window, in (x, y) order.

fn (w: ^Window) getCursorPos*(): (int, int)

fn (^Window) getCursorX

Shorthand for w.getCursorPos().item0.

fn (w: ^Window) getCursorX*(): int { return w.getCursorPos().item0 }

fn (^Window) getCursorY

Shorthand for w.getCursorPos().item1.

fn (w: ^Window) getCursorY*(): int { return w.getCursorPos().item1 }

fn (^Window) moveCursor

Moves the cursor by (dx,dy). Reports whether it was able to do so.

fn (w: ^Window) moveCursor*(dx, dy: int): bool

fn (^Window) moveAndClampCursor

Moves the cursor by (dx,dy). Clamps the resulting position to the window's bounds. Returns the actual position it reached, in (x,y) order as well.

fn (win: ^Window) moveAndClampCursor*(dx, dy: int): (int, int)

fn (^Window) clearToEOL

Clears the contents from the cursor's location to the end of the line. Reports whether it managed to do so.

fn (w: ^Window) clearToEOL*(): bool

import (
	"std.um"

	"umbox/fmt/fmt.um"
)

type (
	RawWindow = ^void
	RawTerminal = ^void
	RawColorPair = uint
	RawAttribute = int
	RawStandardColor = int
)

//~~type Window and type Terminal
// The main abstractions for a terminal and a window in the library.
type (
	Window* = struct { _: RawWindow
		// Reference back to the terminal owning this window.
		terminal: weak ^Terminal
	}

	Terminal* = struct { _: RawTerminal
		// The main window owned by this terminal.
		window: Window

		// The maximum amount of colors supported in this terminal.
		// Set on creation (see `fn mkTerminal`).
		maxColors: int

		// The amount of color pairs currently allocated.
		// Used internally, but can be modified.
		pairAmt: int
	}
)
//~~

fn umc__errno(): int
fn umc__strerror(errno: int): str

fn errFromCode(): std::Err {
	errno := umc__errno()
	return std::error(errno, umc__strerror(errno), "catcurses.um")
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

fn umc__set_term(term: RawTerminal): RawTerminal

var curTerm, stdTerm: ^Terminal

fn makeCurrent(t: ^Terminal): bool {
	curTerm = t
	return umc__set_term(t._) != null
}

//~~fn sessionExists
// Reports whether a terminal is set.
// (In most cases, this serves to check whether ncurses is initialized.)
fn sessionExists*(): bool
//~~

// this function is funny
fn sessionExists*(): bool { return curTerm != null }

//~~fn curTerminal
// Returns the current terminal.
// See `fn (^Terminal) makeCurrent`.
fn curTerminal*(): ^Terminal
//~~

fn curTerminal*(): ^Terminal { return curTerm }

//~~fn (^Terminal) makeCurrent
// Makes `t` the "current" terminal; that is, the terminal whose associated window is shown on-screen.
// Reports whether `t` was successfully set as the current terminal.
fn (t: ^Terminal) makeCurrent*(): bool
//~~

fn (t: ^Terminal) makeCurrent*(): bool { return makeCurrent(t) }

//~~fn stdTerminal
// Returns the standard terminal.
// If ncurses hasn't been initialized yet, this function will initialize it.
fn stdTerminal*(): ^Terminal
//~~

fn umc__initscr(): RawWindow

fn stdTerminal*(): ^Terminal {
	if stdTerm == null {
		w := umc__initscr()
		std::assert(w != null, "couldn't initialize ncurses")

		// this is ugly but meh
		s := umc__set_term(null)
		umc__set_term(s)
		stdTerm = &Terminal{ _: s }
		stdTerm.window = { _: w, terminal: stdTerm }
		stdTerm.makeCurrent()

		std::assert(w == stdTerm.window._,
			sprintf("initscr window doesn't match stdTerm.window._: %llv vs %llv", w, stdTerm.window._))
	}
	return stdTerm
}

//~~fn mkTerminal
// Creates a new terminal.
// If `termType` isn't empty, the created terminal is created with said type.
// If `outf` and `inf` are null, they default to `std::stdout()` and `std::stdin()`, respectively.
//
// If no terminal existed previously, initializes ncurses and returns a stub representing the standard terminal.
// `termType`, `outf` and `inf` are ignored in this case.
fn mkTerminal*(termType: str = "", outf: std::File = null, inf: std::File = null): (^Terminal, std::Err)
//~~

fn umc__newterm(termType: str, outf, inf: std::File): RawTerminal
fn umc__curscr(): RawWindow

fn mkTerminal*(termType: str = "", outf: std::File = null, inf: std::File = null): (^Terminal, std::Err) {
	if stdTerm == null { return stdTerminal(), {} }

	if termType == "" { termType = std::getenv("TERM") }
	if outf == null   { outf     = std::stdout()       }
	if inf == null    { inf      = std::stdin()        }

	raw := umc__newterm(termType, std::stdout(), std::stdin())
	if raw == null { return null, errFromCode() }

	term := &Terminal{ _: raw }
	old := umc__set_term(raw)
	w := umc__curscr()
	umc__set_term(old)
	term.window = { _: w, terminal: term }
	return term, {}
}

//~~type ColorPairID
// Identifies a specific, preinitialized color pair.
type ColorPairID* = int
//~~

//~~fn (^ColorPairID) string
// Returns the string representation of this color pair identifier.
// For debugging purposes.
fn (c: ^ColorPairID) string*(): str { return sprintf("", c^) }
//~~

fn umc__toRawColorPair(a: ColorPairID): RawColorPair

fn (a: ^ColorPairID) raw(): uint { return umc__toRawColorPair(a^) }

//~~type StandardColor
// Indices to the standard terminal colors.
type StandardColor* = enum {
	black
	red
	green
	yellow
	blue
	magenta
	cyan
	white

	// "Bright" colors
	brightBlack
	brightRed
	brightGreen
	brightYellow
	brightBlue
	brightMagenta
	brightCyan
	brightWhite
}

// Aliases
const (
	gray* = StandardColor.white
	darkGray* = StandardColor.brightBlack
)
//~~

const maxStandardColors = int(StandardColor.brightWhite)+1

//~~fn (^Terminal) supportsColors
// Reports whether this terminal supports colors.
fn (t: ^Terminal) supportsColors*(): bool
//~~

fn umc__has_colors_sp(term: RawTerminal): bool

fn (t: ^Terminal) supportsColors*(): bool { return umc__has_colors_sp(t._) }

//~~fn (^Terminal) canChangeColors
// Reports whether this terminal can change its colors.
fn (t: ^Terminal) canChangeColors*(): bool
//~~

fn umc__can_change_color_sp(term: RawTerminal): bool

fn (t: ^Terminal) canChangeColors*(): bool { return umc__can_change_color_sp(t._) }

//~~fn (^Terminal) enableColors
// Enables color capability. Returns whether it was enabled.
fn (t: ^Terminal) enableColors*(): bool
//~~

fn umc__start_color_sp(term: RawTerminal): bool
fn umc__getCurrentMaxColors(): int

fn (t: ^Terminal) enableColors*(): bool {
	if umc__start_color_sp(t._) {
		t.maxColors = umc__getCurrentMaxColors()
		return true
	}
	return false
}

//~~fn (^Terminal) setColorPair
// Associates `pair` with `bg` and `fg` as background/foreground colors.
fn (t: ^Terminal) setColorPair*(pair: ColorPairID, bg, fg: StandardColor): bool
//~~

fn umc__toRawStandardColor(c: StandardColor): RawStandardColor
fn umc__init_pair_sp(term: RawTerminal, pair: ColorPairID, bg, fg: RawStandardColor): bool

fn (t: ^Terminal) setColorPair*(pair: ColorPairID, bg, fg: StandardColor): bool {
	return umc__init_pair_sp(t._, pair,
		umc__toRawStandardColor(bg),
		umc__toRawStandardColor(fg)
	)
}

//~~fn (t: ^Terminal) addColorPair
// Allocates a new color pair, with `bg` and `fg` as background/foreground colors, and returns its ID.
fn (t: ^Terminal) addColorPair*(bg, fg: StandardColor): ColorPairID
//~~

fn (t: ^Terminal) addColorPair*(bg, fg: StandardColor): ColorPairID {
	t.pairAmt++ // 1-indexed, cause IIRC the 0th pair is reserved
	newPair := t.pairAmt
	if !t.setColorPair(newPair, bg, fg) { return -1 }
	return newPair
}

//~~fn (^Terminal) setColor
// Changes the RGB values associated with `color` to `r`, `g` and `b`.
// `r`, `g` and `b` should be normalized floats (0-1, they will be clamped if outside that range),
// and will be quantized to a 0-1000 integer range for ncurses.
//
// This function will affect every color pair using `color` as a palette entry.
//
// It will do nothing (and return `false`) if:
// - the terminal doesn't support colors (see `fn (^Terminal) supportsColors`),
// - the colors are unable to be changed (see `fn (^Terminal) canChangeColors`), or
// - `color` is outside the range of colors the terminal can support (see `type Terminal`)
fn (t: ^Terminal) setColor*(color: StandardColor, r, g, b: real): bool
//~~

fn umc__init_color_sp(term: RawTerminal, color, r, g, b: int): bool

fn (t: ^Terminal) setColor*(color: StandardColor, r, g, b: real): bool {
	if !(t.supportsColors() && t.canChangeColors())     { return false }

	raw := int(color)
	if raw < 0 || raw >= t.maxColors { return false }

	if r < 0.0 { r = 0.0 } else if r >= 1.0 { r = 1.0 }
	if g < 0.0 { g = 0.0 } else if g >= 1.0 { g = 1.0 }
	if b < 0.0 { b = 0.0 } else if b >= 1.0 { b = 1.0 }

	ir, ig, ib := round(r * 1000.0), round(g * 1000.0), round(b * 1000.0)

	return umc__init_color_sp(t._, raw, ir, ig, ib)
}

//~~fn (^Terminal) setColorBytes
// Alternate version of `fn (^Terminal) setColor` that takes in byte values (0-255).
fn (t: ^Terminal) setColorBytes*(color: StandardColor, r, g, b: uint8): bool
//~~

fn (t: ^Terminal) setColorBytes*(color: StandardColor, r, g, b: uint8): bool {
	return t.setColor(color, real(r) / 255.0, real(g) / 255.0, real(b) / 255.0)
}

//~~fn (^Terminal) setColorHex
// Alternate version of `fn (^Terminal) setColor` that takes in a full hex value (0xRRGGBB).
fn (t: ^Terminal) setColorHex*(color: StandardColor, rgb: uint32): bool
//~~

fn (t: ^Terminal) setColorHex*(color: StandardColor, rgb: uint32): bool {
	return t.setColorBytes(color,
		uint8((rgb >> 16) & 0xff),
		uint8((rgb >> 8) & 0xff),
		uint8(rgb & 0xff)
	)
}

//~~fn (^Terminal) destroy
// Destroys the underlying terminal. `t` is no longer valid for use after calling this method.
// If `t` is the standard terminal, this method will finalize ncurses.
fn (t: ^Terminal) destroy*()
//~~

fn umc__endwin(): bool
fn umc__delscreen(term: RawTerminal)

fn (t: ^Terminal) destroy*() {
	if t == stdTerm {
		umc__endwin()
		return
	}

	if curTerm == t { makeCurrent(stdTerminal()) }
	umc__delscreen(t._)
}

fn assertStdTerm(t: ^Terminal, func: str) {
	std::assert(t == stdTerm, sprintf("can't call '%s' on anything other than the standard terminal", func))
}

//~~fn (^Terminal) cbreak
// Enables/disables cbreak mode (disables line buffering but still interprets signals) for the terminal.
// Must only be called on the standard terminal (see `fn stdTerminal`).
// Reports whether the terminal was set/unset to cbreak mode.
fn (t: ^Terminal) cbreak*(b: bool): bool
//~~

fn umc__cbreak(): bool
fn umc__nocbreak(): bool

fn (t: ^Terminal) cbreak*(b: bool): bool {
	assertStdTerm(t, "cbreak")
	if b { return umc__cbreak() }
	return umc__nocbreak()
}

//~~fn (^Terminal) echo
// Enables/disables character echoing after each keypress.
// Must only be called on the standard terminal (see `fn stdTerminal`).
// Reports whether the echo was able to be set/unset.
fn (t: ^Terminal) echo*(b: bool): bool
//~~

fn umc__echo(): bool
fn umc__noecho(): bool

fn (t: ^Terminal) echo*(b: bool): bool {
	assertStdTerm(t, "echo")
	if b { return umc__echo() }
	return umc__noecho()
}

//~~fn (^Terminal) nl
// Controls translation of the return key into NL (10, 0x0a, '\n').
// This allows for detecting whether the return key was pressed, amongst other benefits (see `man 3x nl`).
// Must only be called on the standard terminal (see `fn stdTerminal`).
// Reports whether the translation was able to be enabled/disabled.
fn (t: ^Terminal) nl*(b: bool): bool
//~~

fn umc__nl(): bool
fn umc__nonl(): bool

fn (t: ^Terminal) nl*(b: bool): bool {
	assertStdTerm(t, "nl")
	if b { return umc__nl() }
	return umc__nonl()
}

//~~fn (^Terminal) raw
// Enables/disables raw mode for the terminal.
// Must only be called on the standard terminal (see `fn stdTerminal`).
// Reports whether the terminal was set/unset to raw mode.
fn (t: ^Terminal) raw*(b: bool): bool
//~~

fn umc__raw(): bool
fn umc__noraw(): bool

fn (t: ^Terminal) raw*(b: bool): bool {
	assertStdTerm(t, "raw")
	if b { return umc__raw() }
	return umc__noraw()
}

//~~type Visibility
// Cursor visibility specifiers. See `fn (^Terminal) cursorVisibility`.
type Visibility* = enum { hidden; visible; veryVisible }
//~~

//~~fn (^Visibility) string
// Returns the string representation of this visibility specifier.
// For debugging purposes.
fn (v: ^Visibility) string*(): str
//~~

fn (v: ^Visibility) string*(): str {
	switch v^ {
	case .hidden:      return "hidden"
	case .visible:     return "visible"
	case .veryVisible: return "veryVisible"
	}
	std::assert(false, "unreachable")
	return ""
}

//~~fn (^Terminal) cursorVisibility
// Changes the cursor visibility to `vis` (see `type Visibility`).
// Must only be called on the standard terminal (see `fn stdTerminal`).
// Returns the previous visibility and whether it was able to change it at all.
fn (t: ^Terminal) cursorVisibility*(v: Visibility): (Visibility, bool)
//~~

fn umc__curs_set(v: Visibility, prev: ^Visibility): bool

fn (t: ^Terminal) cursorVisibility*(v: Visibility): (Visibility, bool) {
	var prev: Visibility
	// I love that the enum maps directly to the value required
	if !umc__curs_set(v, &prev) { return .hidden, false }
	return prev, true
}

//~~fn (^Terminal) escapeDelay
// Changes the amount of time ncurses waits for further input to disambiguate escapes from escape sequences.
// Only applicable when `keypad(true)`; otherwise, the program must disambiguate escapes itself.
fn (t: ^Terminal) escapeDelay*(n: int)
//~~

fn umc__setEscDelay(n: int)

fn (t: ^Terminal) escapeDelay*(n: int) { umc__setEscDelay(n) }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//~~type Key
// The enum wrapping the value returned by `fn (^Window) getKey`.
// If getKey returns an ASCII keypress, the value wrapped under this enum will be that key.
//
// > [!WARNING]
// > The function keys (F0..F12 and arrow keys, amongst others)
// > *do not match* the values used by ncurses, and don't currently intend to do so.
type Key* = enum {
	tab = 9
	newline = 10
	enter = 13 // '\r' in raw mode

	escape = 0x1b

	// Function keys
	f0 = 1000; f1; f2; f3; f4; f5; f6; f7; f8; f9; f10; f11; f12

	down; up; left; right // Arrow keys
	pageUp; pageDown
	home; end
	insert; delete
	backspace

	// No key was pressed; applicable when
	// `timeout` or `noDelay` have been set
	// (technical detail: returned when errno = EAGAIN)
	none
}
//~~

//~~fn (^Key) string
// Returns the string representation of this key.
// For debugging purposes.
fn (k: ^Key) string*(): str
//~~

var keyStringReprs: map[Key]str

// TODO(thacuber2a03): should likely automate this
fn (k: ^Key) string*(): str {
	if !valid(keyStringReprs) {
		keyStringReprs = {
			.escape: "escape",

			.f0: "f0", .f1: "f1", .f2: "f2", .f3: "f3", .f4: "f4", .f5: "f5", .f6: "f6",
			.f7: "f7", .f8: "f8", .f9: "f9", .f10: "f10", .f11: "f11", .f12: "f12",

			.down: "down", .up: "up", .left: "left", .right: "right",

			.pageUp: "pageUp", .pageDown: "pageDown",
			.home: "home", .end: "end",
			.delete: "delete", .backspace: "backspace",

			.none: "none",
		}
	}

	return keyStringReprs[k^]
}

// for `fn (^Window) getKey` and for `keys` array reference in catcurses.c
const otherKeyStart = int(Key.down)

//~~fn keyF
// Similar to ncurses' `KEY_F(n)` macro.
fn keyF*(i: int): Key { return Key(int(Key.f0)+i) }
//~~

//~~const ctrlMask
// The bitmask pertaining to CTRL+char presses.
const ctrlMask* = uint8(0x1f)
//~~

//~~fn ctrlKey
// Returns the CTRL representation of `k`.
fn ctrlKey*(k: Key): Key { return Key(int(k) & ctrlMask) }
//~~

//~~fn (^Window) keypad
// Attempts to enable/disable function key detection in `w`.
// Reports whether it was able to do so.
fn (w: ^Window) keypad*(enable: bool): bool
//~~

fn umc__keypad(win: RawWindow, enable: bool): bool
fn (w: ^Window) keypad*(enable: bool): bool { return umc__keypad(w._, enable) }

//~~fn (^Window) noDelay
// Disables delay entirely (makes `fn (^Window) getKey` and related non-blocking).
// Reports whether the operation succeeded.
fn (w: ^Window) noDelay*(b: bool): bool
//~~

fn umc__nodelay(win: RawWindow, bf: bool): bool

fn (w: ^Window) noDelay*(b: bool): bool { return umc__nodelay(w._, b) }

//~~
// Constants for `fn (^Window) timeout`.
const (
	blocking* = -1
	nonBlocking*
)
//~~

//~~fn (^Window) timeout
// Sets the timeout for reading operations (`fn (^Window) getKey` and others).
// Fine-grained; has millisecond-level control, as opposed to `man 3x halfdelay`.
//
// If `n`:
// - = `-1`/`blocking`, calls to `getKey` and friends will wait forever.
// - = `0`/`nonBlocking`, calls to `getKey` and friends will return immediately.
// - Otherwise, waits up to `n` milliseconds.
fn (w: ^Window) timeout*(n: int)
//~~

fn umc__wtimeout(win: RawWindow, delay: int)

fn (w: ^Window) timeout*(n: int) { umc__wtimeout(w._, n) }

//~~fn (^Window) setCursorPos
// Moves the cursor to (`x`,`y`), relative to this window.
// Reports whether it was able to do so.
fn (w: ^Window) setCursorPos*(x, y: int): bool
//~~

fn umc__wmove(win: RawWindow, x, y: int): bool

fn (w: ^Window) setCursorPos*(x, y: int): bool { return umc__wmove(w._, x, y) }

//~~fn (^Window) writeChar
// Writes `c` at the current position of the cursor.
// Reports whether it was able to do so.
fn (w: ^Window) writeChar*(c: char): bool
//~~

fn umc__waddch(w: RawWindow, c: char): bool

fn (w: ^Window) writeChar*(c: char): bool { return umc__waddch(w._, c) }

fn umc__waddnstr(win: RawWindow, s: str, n: int): bool

//~~fn (^Window) writeString
// Writes `n` bytes of `s` at the current position of the cursor.
// If `n` == `-1`, writes the whole string. (default behavior)
// Reports whether it could be written.
// Fails if `n` != `-1` and `n` > `len(s)`.
fn (w: ^Window) writeString*(s: str, n: int = -1): bool
//~~

fn (w: ^Window) writeString*(s: str, n: int = -1): bool {
	if n != -1 && n > len(s) { return false }
	return umc__waddnstr(w._, s, n) 
}

//~~fn (^Window) writeStringAt
// Analogous to `fn (^Window) writeString`, but moves the cursor to (`x`,`y`).
fn (w: ^Window) writeStringAt*(x, y: int, s: str, n: int = -1): bool
//~~

fn (w: ^Window) writeStringAt*(x, y: int, s: str, n: int = -1): bool {
	if !w.setCursorPos(x, y) { return false }
	return w.writeString(s, n)
}

//~~fn (^Window) print
// Prints a formatted string at the current position of the cursor.
// Reports whether it could be written.
// 
// The format string follows [fmt.um](https://umbox.tophat2d.dev/package/fmt/browse#:~:text=Syntax)'s syntax.
fn (w: ^Window) print*(fmt: str, a: ..any): bool
//~~

fn (w: ^Window) print*(fmt: str, a: ..any): bool { return w.writeString(fmt::vsfmt(fmt, a)) }

//~~fn (^Window) printAt
// Moves the cursor to (`x`,`y`) and prints a formatted string there.
// Reports whether it could be written.
//
// The format string follows [fmt.um](https://umbox.tophat2d.dev/package/fmt/browse#:~:text=Syntax)'s syntax.
fn (w: ^Window) printAt*(x, y: int, fmt: str, a: ..any): bool
//~~

fn (w: ^Window) printAt*(x, y: int, fmt: str, a: ..any): bool { return w.writeStringAt(x, y, fmt::vsfmt(fmt, a)) }

//~~fn (^Window) getKey
// Returns the current keystroke, if any.
// Return values depend on the standard terminal's settings; check `man 3x getch` for information.
fn (w: ^Window) getKey*(): (Key, std::Err)
//~~

fn umc__wgetch(win: RawWindow, c: ^int): bool
fn umc__errnoIsAgain(): bool
fn umc__fnKeyOffset(key: int): int
fn umc__otherKeyOffset(key: int): int

fn (w: ^Window) getKey*(): (Key, std::Err) {
	var c: int
	if !umc__wgetch(w._, &c) {
		if umc__errnoIsAgain() { return .none, {} }
		return .none, errFromCode() 
	}

	if c >= 256 {
		if offset := umc__fnKeyOffset(c); offset >= 0 && offset <= 12 {
			return keyF(offset), {}
		} else if offset := umc__otherKeyOffset(c); offset != -1 {
			return Key(otherKeyStart+offset), {}
		}
	}

	return Key(c), {}
}

//~~fn (^Window) refresh
// Redraws this window, if applicable.
fn (w: ^Window) refresh*(): bool
//~~

fn umc__wrefresh(win: RawWindow): bool
fn (w: ^Window) refresh*(): bool { return umc__wrefresh(w._) }

//~~type Attribute
// Color-independent character attributes.
type Attribute* = enum {
	normal     // Normal display (no highlight)
	standOut   // Best highlighting mode of the terminal
	underline  // Underlining
	reverse    // Reverse video
	blink      // Blinking
	dim        // Half bright
	bold       // Extra bright or bold
	protect    // Protected mode
	invis      // Invisible or blank mode
	altCharset // Alternate character set
	italic     // Italics (non-X/Open extension)
}
//~~

const charAttributesAmt = int(Attribute.italic)+1

//~~fn (^Attribute) string
// Returns the string representation of this character attribute.
// For debugging purposes.
fn (a: ^Attribute) string*(): str
//~~

fn (a: ^Attribute) string*(): str {
	switch a^ {
	case .normal:     return "normal"
	case .standOut:   return "standOut"
	case .underline:  return "underline"
	case .reverse:    return "reverse"
	case .blink:      return "blink"
	case .dim:        return "dim"
	case .bold:       return "bold"
	case .protect:    return "protect"
	case .invis:      return "invis"
	case .altCharset: return "altCharset"
	case .italic:     return "italic"
	}
	std::assert(false, "unreachable")
	return ""
}

fn umc__toRawAttr(a: Attribute): RawAttribute

fn (a: ^Attribute) raw(): uint { return umc__toRawAttr(a^) }

// fn umc__from_raw_attr(a: RawAttribute): Attribute
fn umc__wattr_on(win: RawWindow, a: RawAttribute): bool
fn umc__wattr_off(win: RawWindow, a: RawAttribute): bool
fn umc__wattr_get(win: RawWindow, attr: ^RawAttribute, pair: ^ColorPairID): bool
fn umc__wattr_set(win: RawWindow, attr: RawAttribute, pair: ColorPairID): bool

//~~fn (^Window) attrOn
// Enables all the attributes listed in `attrs` (see `type Attribute`).
// Reports whether any of the attributes could not be enabled, and which one.
fn (w: ^Window) attrOn*(attrs: ..Attribute): (bool, Attribute)
//~~

//~~fn (^Window) attrOff
// Disables all the attributes listed in `attrs` (see `type Attribute`).
// Reports whether any of the attributes could not be disabled, and which one.
fn (w: ^Window) attrOff*(attrs: ..Attribute): (bool, Attribute)
//~~

//~~fn (^Window) attrListOn
// Alternate version of `fn (^Window) attrOn` that explicitly takes a list.
fn (w: ^Window) attrListOn*(attrs: []Attribute): (bool, Attribute)
//~~

//~~fn (^Window) attrListOff
// Alternate version of `fn (^Window) attrOff` that explicitly takes a list.
fn (w: ^Window) attrListOff*(attrs: []Attribute): (bool, Attribute)
//~~

fn (w: ^Window) attrListOn*(attrs: []Attribute): (bool, Attribute) {
	for _,a in attrs { if !umc__wattr_on(w._, a.raw()) { return false, a } }
	return true, .normal
}

fn (w: ^Window) attrListOff*(attrs: []Attribute): (bool, Attribute) {
	for _,a in attrs { if !umc__wattr_off(w._, a.raw()) { return false, a } }
	return true, .normal
}

fn (w: ^Window) attrOn*(attrs: ..Attribute): (bool, Attribute)  { return w.attrListOn(attrs)  }
fn (w: ^Window) attrOff*(attrs: ..Attribute): (bool, Attribute) { return w.attrListOff(attrs) }

//~~fn (^Window) getAttributes
// Returns the set of attributes and color pair currently applied to this window.
// Returns `null` for the attributes map on error.
fn (w: ^Window) getAttributes*(): (^map[Attribute]bool, ColorPairID)
//~~

//~~fn (^Window) setAttributes
// Overwrites the set attributes and color pair currently set for this window.
fn (w: ^Window) setAttributes*(attrs: map[Attribute]bool, pair: ColorPairID): bool
//~~

fn (w: ^Window) getAttributes*(): (^map[Attribute]bool, ColorPairID) {
	var (
		attr: RawAttribute
		pair: ColorPairID
	)

	if !umc__wattr_get(w._, &attr, &pair) { return null, -1 }

	attrs := &map[Attribute]bool{}
	for i := 0; i < charAttributesAmt; i++ {
		a := Attribute(i)
		if attr & a.raw() != 0 { attrs[a] = true }
	}
	return attrs, pair
}

fn (w: ^Window) setAttributes*(attrs: map[Attribute]bool, pair: ColorPairID): bool {
	var raw: RawAttribute
	for a,b in attrs { if b { raw |= a.raw() } }
	return umc__wattr_set(w._, raw, pair)
}

//~~fn (^Window) useColorPair
// Sets `pair` as the current color pair for this window's text output.
// Reports whether it could not be set.
fn (w: ^Window) useColorPair*(pair: ColorPairID): bool
//~~

//~~fn (^Window) detachColorPair
// Stops using `pair` as the current color pair for this window's text output.
// Reports whether it could not be disabled.
fn (w: ^Window) detachColorPair*(pair: ColorPairID): bool
//~~

fn (w: ^Window) useColorPair*(pair: ColorPairID): bool    { return umc__wattr_on(w._, pair.raw())  }
fn (w: ^Window) detachColorPair*(pair: ColorPairID): bool { return umc__wattr_off(w._, pair.raw()) }

//~~type WithFn
// Function type taken as argument by all `fn (^Window) with*` functions.
type WithFn* = fn(win: ^Window)
//~~

//~~fn (^Window) withAttrs
// Runs `f` with all attributes in `attrs` enabled, and disables them when the function ends.
fn (w: ^Window) withAttrs*(attrs: []Attribute, f: WithFn)
//~~

fn (w: ^Window) withAttrs*(attrs: []Attribute, f: WithFn) {
	a, p := w.getAttributes()
	std::assert(a != null, "couldn't get attributes for withAttrs")
	w.attrListOn(attrs)
	f(w)
	w.setAttributes(a^, p)
}

//~~fn (^Window) withColorPair
// Runs `f` with `pair` set as the current color pair, and unsets it when the function ends.
fn (w: ^Window) withColorPair*(pair: ColorPairID, f: WithFn)
//~~

fn (w: ^Window) withColorPair*(pair: ColorPairID, f: WithFn) {
	a, p := w.getAttributes()
	std::assert(a != null, "couldn't get color pair for withColorPair")
	w.useColorPair(pair)
	f(w)
	w.setAttributes(a^, p)
}

//~~fn (^Window) withAttrsAndColorPair
// Shorthand for:
// ```go
// win.withAttrs(attrs, { win.withColorPair(pair, f) })
// ```
fn (w: ^Window) withAttrsAndColorPair*(attrs: []Attribute, pair: ColorPairID, f: WithFn)
//~~

fn (w: ^Window) withAttrsAndColorPair*(attrs: []Attribute, pair: ColorPairID, f: WithFn) {
	w.withAttrs(attrs, |pair, f| { win.withColorPair(pair, f) })
}

//~~fn (^Window) clear
// Clears this window.
fn (w: ^Window) clear*(): bool
//~~

fn umc__clear(win: RawWindow): bool
fn (w: ^Window) clear*(): bool { return umc__clear(w._) }

//~~fn (^Window) erase
// Fills this window with blank characters.
fn (w: ^Window) erase*(): bool
//~~

fn umc__erase(win: RawWindow): bool
fn (w: ^Window) erase*(): bool { return umc__erase(w._) }

//~~fn (^Window) getSize
// Returns the width and height of the window, in that order.
fn (w: ^Window) getSize*(): (int, int)
//~~

fn umc__getmaxxy(win: RawWindow, x, y: ^int)

fn (w: ^Window) getSize*(): (int, int) {
	var x, y: int
	umc__getmaxxy(w._, &x, &y)
	return x, y
}

//~~fn (^Window) getWidth
// Returns the width of the window.
// Shorthand for `w.getSize().item0`.
fn (w: ^Window) getWidth*(): int { return w.getSize().item0 }
//~~


//~~fn (^Window) getHeight
// Returns the height of the window.
// Shorthand for `w.getSize().item1`.
fn (w: ^Window) getHeight*(): int { return w.getSize().item1 }
//~~

//~~fn (^Window) getCursorPos
// Returns the position of the cursor relative to this window, in (`x`, `y`) order.
fn (w: ^Window) getCursorPos*(): (int, int)
//~~

fn umc__getxy(win: RawWindow, x, y: ^int)

fn (w: ^Window) getCursorPos*(): (int, int) {
	var x, y: int
	umc__getxy(w._, &x, &y)
	return x, y
}

//~~fn (^Window) getCursorX
// Shorthand for `w.getCursorPos().item0`.
fn (w: ^Window) getCursorX*(): int { return w.getCursorPos().item0 }
//~~

//~~fn (^Window) getCursorY
// Shorthand for `w.getCursorPos().item1`.
fn (w: ^Window) getCursorY*(): int { return w.getCursorPos().item1 }
//~~

//~~fn (^Window) moveCursor
// Moves the cursor by (`dx`,`dy`). Reports whether it was able to do so.
fn (w: ^Window) moveCursor*(dx, dy: int): bool
//~~

fn (w: ^Window) moveCursor*(dx, dy: int): bool {
	x, y := w.getCursorPos()
	return w.setCursorPos(x+dx, y+dy)
}

//~~fn (^Window) moveAndClampCursor
// Moves the cursor by (`dx`,`dy`). Clamps the resulting position to the window's bounds.
// Returns the actual position it reached, in (`x`,`y`) order as well.
fn (win: ^Window) moveAndClampCursor*(dx, dy: int): (int, int)
//~~

fn (win: ^Window) moveAndClampCursor*(dx, dy: int): (int, int) {
	x, y := win.getCursorPos()
	w, h := win.getSize()

	nx, ny := x+dx, y+dy

	if nx < 0 { nx = 0 }; if nx >= w { nx = w - 1 }
	if ny < 0 { ny = 0 }; if ny >= h { ny = h - 1 }

	win.setCursorPos(nx, ny)
	return nx, ny
}

//~~fn (^Window) clearToEOL
// Clears the contents from the cursor's location to the end of the line.
// Reports whether it managed to do so.
fn (w: ^Window) clearToEOL*(): bool
//~~

fn umc__wclrtoeol(win: RawWindow): bool

fn (w: ^Window) clearToEOL*(): bool { return umc__wclrtoeol(w._) }