Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terminal I/O. #62

Draft
wants to merge 32 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7aca316
Terminal I/O.
sunfishcode Feb 8, 2025
27fd0c1
Update wit-0.3.0-draft/terminal.wit
sunfishcode Feb 8, 2025
13ab8d5
Fill in the current TODO comments.
sunfishcode Feb 8, 2025
bb41d10
Better handling of input control codes.
sunfishcode Feb 9, 2025
cd345f6
Fix handling of U+D in non-immediate mode.
sunfishcode Feb 9, 2025
b2c66d4
Add a TODO about bare Escapes and stream lulls.
sunfishcode Feb 9, 2025
e13b66e
Say more about terminal settings.
sunfishcode Feb 9, 2025
1c73e9e
Give an example of a control key.
sunfishcode Feb 9, 2025
9fc6912
Elaborate on SGR syntax.
sunfishcode Feb 9, 2025
0cc5d84
Define Shift-Tab for input.
sunfishcode Feb 9, 2025
1db49ce
Put emphasis on mode names.
sunfishcode Feb 9, 2025
a80d9b9
Notify applications of resumptions in addition to size changes.
sunfishcode Feb 9, 2025
c150e79
Fix the name of the `color-desired-by-default` variable.
sunfishcode Feb 9, 2025
c4d72d7
fixup for mod<F5>es
sunfishcode Feb 9, 2025
d505a24
Fix the terminfo/termcap names for parameterized cursor movement.
sunfishcode Feb 9, 2025
ab17d03
Specify the default modes for input.
sunfishcode Feb 9, 2025
78f0bba
Clarify interpretations of ASCII names.
sunfishcode Feb 9, 2025
b8a0cb7
Say more about output settings.
sunfishcode Feb 9, 2025
020eeb7
Simplify the refresh key, and fix formatting.
sunfishcode Feb 9, 2025
a2e16d6
Support Bold, Faint, reset, and paste and invisible cursors in more m…
sunfishcode Feb 9, 2025
5530ddb
Add a note about `window-columns`.
sunfishcode Feb 9, 2025
5ca74cd
Add an attribute for the terminal's default colors.
sunfishcode Feb 9, 2025
7826668
Document the state on entry to full-screen mode.
sunfishcode Feb 9, 2025
a14f18b
Update wit-0.3.0-draft/terminal.wit
sunfishcode Feb 10, 2025
78b914e
More reorganization. Add more control codes for output in all modes.
sunfishcode Feb 10, 2025
ba8694d
Add reverse video.
sunfishcode Feb 10, 2025
ffe05de
Fix the terminfo name of End Paste.
sunfishcode Feb 10, 2025
37388d6
Iterate on wording.
sunfishcode Feb 10, 2025
02905b7
Don't trap on invalid output.
sunfishcode Feb 10, 2025
2a76ee6
Remove "may assign other meanings to other control codes".
sunfishcode Feb 10, 2025
e0ffa21
Add a TODO for some things that aren't described yet.
sunfishcode Feb 10, 2025
b6c1539
Describe the behavior of U+8 and U+9 more.
sunfishcode Feb 10, 2025
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
290 changes: 280 additions & 10 deletions wit-0.3.0-draft/terminal.wit
Original file line number Diff line number Diff line change
@@ -1,25 +1,295 @@
/// Terminal input.
/// Support for terminal input.
///
/// In the future, this may include functions for disabling echoing,
/// disabling input buffering so that keyboard events are sent through
/// immediately, querying supported features, and so on.
/// This adds support for a set of terminal control APIs and "ANSI"-style
/// escape sequences and control codes. It is intended to be implementable on a
/// wide variety of platforms, and support the most common features used by
/// terminal applications.
///
/// This specificaion effectively requires implementations to parse the
/// input and output bytestreams and translate escape sequences and control
/// codes to ensure consistent behavior. Implementations shall not permit
/// arbitrary escape sequences to be communicated between applications and
/// virtual terminals.
///
/// All terminal input and output is encoded in UTF-8. No 8-bit
/// "externded ASCII" control code encodings are recognized. On input to
/// applications, any bytes which are not valid UTF-8 are replaced with the
/// replacement character U+FFFD, following the [UTF-8 decoder] algorithm in
/// the [WHATWG Encoding spec], with an [error mode] of "replacement". On
/// output from applications, any bytes which are not valid UTF-8 evoke a
/// trap, following the [UTF-8 decoder] algorithm in the
/// [WHATWG Encoding spec], with an [error mode] of "fatal".
///
/// [UTF-8 decoder]: https://encoding.spec.whatwg.org/#utf-8-decoder
/// [WHATWG Encoding algorithm]: https://encoding.spec.whatwg.org/
/// [error mode]: https://encoding.spec.whatwg.org/#error-mode
///
/// TODO: define lexing and behavior of unrecognized escape codes.
/// TODO: define behavior for unrecognized control codes
///
/// See the comments on the `terminal-output` interface for more background
/// on terminal support.
///
/// In the tables, "terminfo" refers to the long form of the equivalent
/// terminfo capability, "ti" refers to the short form of the equivalent
/// terminfo capability, and "tc" refers to the equivalent termcap name.
/// A "\*" indicates that there is no simple equivalent.
@since(version = 0.3.0)
interface terminal-input {
/// The input side of a terminal.
///
/// Implementations must reset terminals to default settings when terminal
/// resources are created and dropped.
///
/// The following control codes and escape sequences are defined for
/// terminal input:
///
/// Control codes:
///
/// | Code | Meaning | terminfo | ti | tc |
/// | -----| ------------------------------------------------------------ | --------- | ----- | -- |
/// | U+8 | Ctrl-H; despite U+8 being historically called "backspace" in ASCII, this isn't the backspace key | key_left | kcub1 | kl |
/// | U+9 | Tab | \* | \* | \* |
/// | U+A | Enter | key_enter | kent | @8 |
/// | U+C | Ctrl-L, in immediate mode, requests applications refresh the screen | key_clear | kclr | kC |
/// | U+1B | Escape, in immediate mode, | \* | \* | \* |
/// | U+7F | Backspace; this is the backspace key | key_backspace | kbs | kb |
///
/// Escape sequences:
///
/// | Sequence | Meaning | terminfo | ti | tc |
/// | ------------ | ---------------------------------------------------- | --------- | ----- | -- |
/// | `␛[A` | Up | key_up | kcuu1 | ku |
/// | `␛[B` | Down | key_down | kcud1 | kd |
/// | `␛[C` | Right | key_right | kcuf1 | kr |
/// | `␛[D` | Left | key_left | kcub1 | kl |
/// | `␛[F` | End | key_end | kend | @7 |
/// | `␛[H` | Home | key_home | khome | kh |
/// | `␛[2~` | Insert | key_ic | kich1 | kI |
/// | `␛[3~` | Delete | key_dc | kdch1 | kD |
/// | `␛[5~` | Page Up | key_ppage | kpp | kP |
/// | `␛[6~` | Page Down | key_npage | knp | kN |
/// | `␛OP` | F1 | key_f1 | kf1 | k1 |
/// | `␛OQ` | F2 | key_f2 | kf2 | k2 |
/// | `␛OR` | F3 | key_f3 | kf3 | k3 |
/// | `␛OS` | F4 | key_f4 | kf4 | k4 |
/// | `␛[15~` | F5 | key_f5 | kf5 | k5 |
/// | `␛[17~` | F6 | key_f6 | kf6 | k6 |
/// | `␛[18~` | F7 | key_f7 | kf7 | k7 |
/// | `␛[19~` | F8 | key_f8 | kf8 | k8 |
/// | `␛[20~` | F9 | key_f9 | kf9 | k9 |
/// | `␛[21~` | F10 | key_f10 | kf10 | k; |
/// | `␛[23~` | F11 | key_f11 | kf11 | F1 |
/// | `␛[24~` | F12 | key_f12 | kf12 | F2 |
/// | `␛[200~` | Begin Paste; only emitted when bracketed paste mode is activated | PS | PS | \* |
/// | `␛[201~` | End Paste; only emitted when bracketed paste mode is activated | PE | PS | \* |
@since(version = 0.3.0)
resource terminal-input;
resource terminal-input {
/// Enable or disable *immediate* mode.
///
/// Immediate mode makes input key sequences available to be read on
/// the input stream immediately, rather than buffering them up
/// until the end of the line is seen.
///
/// This may fail if the implementation doesn't support immediate mode.
set-immediate: func(mode: bool) -> result;

/// Disable or enable *echo* mode.
///
/// Echo mode retransmits input key sequences back to the output of the
/// terminal, so that users can see what they're typing. Echoing is the
/// default behavior in terminals, but disabling can be useful for
/// entering passwords or for combining with immediate mode to make
/// interactive terminal interfaces.
///
/// This may fail if the implementation doesn't support echo mode.
set-echo: func(mode: bool) -> result;
}
}

/// Terminal output.
/// Support for terminal output.
///
/// This includes support for a basic terminal interactions, including
/// functions for getting and setting basic "termios" terminal attributes, and
/// support for control codes and "ANSI" escape sequences.
///
/// The "ANSI" here refers to ANSI X3.64, which later became ECMA-48
/// (ISO/IEC 6429). However, ECMA-48 was last updated in 1991, it has many
/// features which are no longer relevant, it has no awareness of Unicode
/// or UTF-8, it leaves many behaviors implementation-dependent, and it lacks
/// many extensions that modern implementations have added and have become
/// popular in modern use cases. So while this "ANSI" is the original source
/// for a lot of the terminology used, it's not a normative reference.
///
/// In the future, this may include functions for querying the terminal
/// size, being notified of terminal size changes, querying supported
/// features, and so on.
/// For now, out of practicality, this document starts by describing features
/// which are widely supported and widely used in modern implementations and
/// use cases. Over time, this could grow to become more complete.
@since(version = 0.3.0)
interface terminal-output {
/// The output side of a terminal.
///
/// Implementations must reset terminals to default settings when terminal
/// resources are created and dropped. Implementations are encouraged to
/// ensure that if log messages are also being written to the terminal,
/// that they not be permitted to be obscured by terminal manipulation.
@since(version = 0.3.0)
resource terminal-output;
resource terminal-output {
/// Return the current number of rows and columns in the terminal.
///
/// Not all terminals have a set size, and not all that do know their
/// size, so this function may fail if the size cannot be determined.
window-size: func() -> result<rows-and-columns>;

/// Return the current number of columns in the terminal.
///
/// Not all terminals have a set size, and not all that do know their
/// size, so this function may fail if the size cannot be determined.
window-columns: func() -> result<u16>;

/// Return a `stream` listening for window size changes.
///
/// This `stream` produces empty values when the window size changes.
/// On implementations which don't support size changes, it never
/// produces any values.
size-changes: func() -> stream;

/// What kinds of colors are supported, and preferred?
///
/// This returns a set of flags indicating which families of
/// escape sequences for displaying color are supported. The "OSC 4"
/// method of detecting color is not supported.
///
/// TODO: document `␛[…m` and `␛[48;2;«r»;«g»;«b»m`.
color: func() -> color-flags;

/// Does this terminal support line-editing features?
///
/// These include the control codes and escape sequences for moving
/// the cursor around the current line, clearing all or part of the
/// current line, as well as the "alert" code (U+7) which should
/// produce an acoustic or visual notification. This reflects the
/// functionality commonly used for command-line prompts.
///
/// The following additional control codes and escape sequences are
/// defined in implementations which declare line-enditing support:
///
/// Control codes:
///
/// | Code | Meaning | terminfo | ti | tc |
/// | ---- | -------------------------------------------------------- | --------- | ----- | -- |
/// | U+7 | Alert | bell | bel | bl |
/// | U+8 | Move cursor back one column | cursor_left | cub1 | le |
/// | U+9 | Tab | tab | ht | ta |
/// | U+A | End of line | newline | nel | nw |
/// | U+C | FF Terminal Compatibility | newline | nel | nw |
/// | U+D | Carriage Return | carriage_return | cr | cr |
/// | U+7F | No Effect | | | |
///
/// Escape sequences:
///
/// | Sequence | Meaning | terminfo | ti | tc |
/// | -------- | ---------------------------------------------------- | --------- | ----- | -- |
/// | `␛[K` | Clear to end of line | clr_eol | el | ce |
/// | `␛[0K` | Clear to end of line | clr_eol | el | ce |
/// | `␛[2K` | Clear entire line | \* | \* | \* |
line-editing-supported: func() -> bool;

/// Does this terminal support full-screen features?
///
/// These include moving the cursor to arbitrary positions on the
/// full screen, and clearing all or part of the full screen. This
/// reflects the functionality commonly used for interactive terminal
/// user interfaces.
///
/// The following additional control codes and escape sequences are
/// defined in implementations which declare full-screen support:
///
/// Escape sequences:
///
/// | Sequence | Meaning | terminfo | ti | tc |
/// | ----------- | ------------------------------------------------- | --------- | ----- | -- |
/// | `␛[?1049h` | Enter full-screen mode | enter_ca_mode | smcup | ti |
///
/// The following additional control codes and escape sequences are
/// defined when full-screen mode has been entered:
///
/// | Sequence | Meaning | terminfo | ti | tc |
/// | ----------- | ------------------------------------------------- | --------- | ----- | -- |
/// | `␛[«n»A` | Move the cursor up `«n»` rows | cursor_up | cuu1 | up |
/// | `␛[«n»B` | Move the cursor down `«n»` rows | cursor_down | cud1 | do |
/// | `␛[«n»C` | Move the cursor right `«n»` column | cursor_right | cuf1 | nd |
/// | `␛[«n»D` | Move the cursor left `«n»` columns | cursor_left | cub1 | le |
/// | `␛[«n»G` | Move the cursor to column `«n»` | column_address | hpa | ch |
/// | `␛[«row»;«column»H` | Move the cursor to row `«row»` and column `«column»` | cursor_address | cup | cm |
/// | `␛[0J` | Clear from the cursor to the end of the screen | clr_eos | ed | cd |
/// | `␛[1J` | Clear the screen from the beginning to the current cursor position | \* | \* | \* |
/// | `␛[2J` | Clear the whole screen | clear_screen | clear | cl |
/// | `␛[«n»d` | Move the cursor to row `«n»` | row_address | vpa | cv |
/// | `␛[«row»;«column»f` | Move the cursor to row `«row»` and column `«column»` | cursor_address | cup | cm |
/// | `␛[?25h` | Set the cursor as visible | cursor_visible | cvvis | vs |
/// | `␛[?1049h` | Clear the screen and reset full-screen settings to defaults | enter_ca_mode | smcup | ti |
/// | `␛[?2004h` | Begin bracketed paste mode | BE | BE | \* |
/// | `␛[?25l` | Set the cursor as invisible | cursor_invisible | civis | vi |
/// | `␛[?1049l` | Exit full-screen mode and restore the terminal to its prior state | exit_ca_mode | rmcup | te |
/// | `␛[?2004l` | End bracketed paste mode | BD | BD | \* |
/// | `␛[!p` | Reset the terminal to default settings, without clearing the screen | \* | \* | \* |
full-screen-supported: func() -> bool;
}

/// Flags indicating support for different sets of color escape
/// sequences, and the user's preference for whether they should
/// be used by default.
///
/// Other color features, including 88-color and 256-color are not
/// included here, as the associated escape sequences are not as
/// portable, and they're effectively obviated by truecolor support.
flags color-flags {
/// Are the classic "4-bit color" escape sequences supported?
///
/// This indicates support for up to 16 colors, on foreground and
/// background, using the widely-supported (and ECMA-48) "SGR"
/// color escape sequences of the form `␛[…m`. See
/// [here] for more information.
///
/// Before using color in your user interface, also consider
/// checking `color-desired` to obtain the user's preference for
/// enabling color by default.
///
/// [here]: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
ansi,

/// Are the 24-bit "true color" escape sequences supported?
///
/// This indicates support for up to 16 colors, on foreground and
/// background, using "true color" escape sequences of the form
/// `␛[38;2;«r»;«g»;«b»m` (foreground) and `␛[48;2;«r»;«g»;«b»m`
/// (background). The `«r»`, `«g»`, and `«b»` fields are integers
/// in the range [0,256) indicating red, green, and blue values
/// respectively. See [here] for more information.
///
/// Before using color in your user interface, also consider
/// checking `color-desired` to obtain the user's preference for
/// enabling color by default.
///
/// [here]: https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit
truecolor,

/// Does the user with color to be used by default?
sunfishcode marked this conversation as resolved.
Show resolved Hide resolved
///
/// Some users have a terminal which supports color, but prefer
/// applications not use it by default; this flag indicates
/// this preference.
///
/// See the [`NO_COLOR` website](http://no-color.org/) for more
/// information.
color-desired-by-default,
}

/// A pair of rows and columns.
record rows-and-columns {
rows: u16,
columns: u16,
}
}

/// An interface providing an optional `terminal-input` for stdin as a

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should there also be an interface that's equivalent to opening CON or /dev/tty? this is used by things like sudo tee where the inputs/outputs may be redirected but sudo still accesses the controlling terminal to write out the password prompt and read in the password.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be straightforward to have an API that returns a terminal-input. terminal-output, and streams for the controlling terminal (alongside the stdio functions). That's independent of what the terminal-input and terminal-output interfaces look like though, so that could be proposed separately.

A potential consideration is that we may not always want sandboxed programs to be able to do that, because that would mean they could bypass stdio redirects and potentially grab input from the terminal meant for another application. So we'd want to think through the use cases.

Expand Down