# cterm — Canvas terminal

`cterm` is the `graphics.canvas` terminal emulator shipped as `/bin/cterm`
(`apps/1.19/sys/cterm`). It is different from `xterm`:

| | `xterm` | `cterm` |
| --- | --- | --- |
| Widget | LCDUI `Form` with a `StringItem` buffer | `graphics.canvas` character grid |
| Colours | plain text | ANSI SGR (16, 256 and truecolour) |
| Command line | text field + Run command | in-canvas editor with history |
| Scrollback | Form scrolling | terminal scrollback with Up/Down, game keys and stylus drag |
| Typing | the Form's text field | direct typing (`abc`) or the phone text box (`123`) |

## Running

```sh
cterm          # interactive
cterm 1        # deterministic demo frame, then return (test suite)
```

The app paints a monospace grid, a coloured prompt on the last row and the
command line. Commands run in the cterm process' scope, so `cd` changes the
prompt and the directory used by the next command. Output is captured through
`io.setstdout` and parsed by the terminal engine, which means any program that
prints ANSI escape sequences is rendered with colour.

## Keys

| Key | Action |
| --- | --- |
| Up / Down | scroll the terminal up / down (scrollback) |
| PageUp / PageDown (GAME_A / GAME_B) | page the scrollback |
| Home / End (GAME_C / GAME_D) | start / end of the input line |
| Left / Right | move the input cursor |
| FIRE | open the phone text box (`123`) or run the line (`abc`) |
| Backspace | delete before the cursor (key code 8 or -8) |
| Delete | delete at the cursor |
| Tab | complete a command (`/bin`) or a path |
| printable | insert at the cursor and switch to `abc` |

Up/Down scroll the terminal instead of walking the history: on a phone the
scrollback is what the arrows are for. The history is still reachable through
the **History** command.

## Typing modes

The input row ends with a small `abc` / `123` marker:

- `123` — the device has no usable keyboard, so FIRE opens the native text box,
  fills the command line and runs it when you confirm.
- `abc` — direct typing. Any printable key switches to this mode for the rest
  of the session, and FIRE runs the line immediately.

The **QWERTY** command toggles the mode (and the `qwerty` built-in command does
the same). The choice is remembered in `/home/.ctermrc` (`qwerty=1`), so the
next run starts in the same mode; if the file cannot be written the mode is
still changed for the session.

## Commands (soft keys)

| Command | Effect |
| --- | --- |
| **Type** | open the phone text box for the command line |
| **Send** | run the current line |
| **Switch to...** | open the running-apps menu (`graphics.taskmngr`) |
| **History** | pick a previous command from a list (runs it) |
| **Clear** | clear the screen and the scrollback |
| **Font** | cycle the monospace font size |
| **QWERTY** | toggle direct typing (`abc` / `123`) |
| **Exit** | close cterm |

Dragging with the stylus scrolls the scrollback.

## Built-in commands

These are handled by cterm before the shell:

| Command | Effect |
| --- | --- |
| `clear` | clear the screen and the scrollback |
| `colors` | print the ANSI palette and style samples |
| `history` | list the command history |
| `qwerty` | toggle the `abc` / `123` typing mode |
| `help` | key and command reference |
| `exit` / `logout` | close cterm |

Everything else goes through `os.execute` (`/bin/sh` built-ins, `/bin/*`
programs, pipes and redirections included).

## Terminal engine

The file has two halves. The first is a plain Lua engine (`cterm.new`) with no
dependency on the canvas:

- a `cols × rows` grid where each row is a string of exactly `cols` characters
  plus a list of styled runs (`{ x, width, fg, bg, at }`, with the attribute
  bits bold, underline, reverse, dim and hidden). Writing text splices the row
  string and appends or updates one run instead of touching one table slot per
  character, which is what makes output affordable on a phone; a `revision`
  counter is bumped once per write;
- ANSI parsing: SGR (including `38;5;n`, `48;5;n`, `38;2;r;g;b`), cursor
  movement (`A B C D E F G H f d`), erase (`J K X`), insert/delete lines and
  characters (`L M @ P`), scroll (`S T`), save/restore (`s u`) and cursor
  visibility (`?25h/l`);
- wrapping, CR/LF/BS/TAB handling, delayed scrollback and a capped history;
- an editable input line (`setInput`, `insertChar`, `backspaceInput`,
  `deleteInput`, `moveInput`, `moveHome`, `moveEnd`, history navigation);
- `screenText`, `scrollbackText`, `allText`, `rowText` and `cellAt` for tests
  and diagnostics.

Text is parsed in runs: `write` finds the next newline or escape with
`string.find` and hands the plain text between them to `writeText`, so a
control-free chunk costs a few native string operations. CSI sequences are
located by scanning for their final byte eight bytes at a time. Parsed CSI
parameters and SGR state transitions are cached (coloured output repeats the
same sequences on every line), and the attribute state is a single `bits`
number, so a cached transition is just `(fg, bg, bits)` before and after.

The second half (`cterm.run`) owns the canvas: it measures the font, creates
the grid, draws background runs and text runs, paints the prompt and cursor,
registers keys/pointer/commands and executes shell commands.

The engine can be loaded without starting the app:

```lua
CTERM_AS_MODULE = true
local cterm = load(io.read("/bin/cterm"))
CTERM_AS_MODULE = nil

local t = cterm.new({ cols = 80, rows = 24 })
t:write("\27[31mred\27[0m and \27[1mbold\27[0m\n")
print(t:screenText())
```

## Staying responsive on a phone

Four rules keep the MIDlet's LCDUI event thread free and the battery out of it:

- The grid is redrawn only when the terminal changed: `Terminal.revision` is
  bumped once per write, so `render()` compares it (plus the scroll offset and
  the geometry) before repainting the whole screen. A repaint replays the rows'
  runs, so it never scans cells.
- The input row is keyed by `(input, cursor, mode, prompt width)`, so a key that
  changes nothing costs no drawing and no repaint at all.
- A key press repaints only the input row (prompt, line, cursor, mode marker),
  which is a handful of canvas calls instead of a full grid pass.
- Shell commands run on a background thread (`java.run`), so a slow command
  never freezes the event loop. The canvas is already current, so the job only
  posts a repaint; `c:display()` is left to the UI callbacks.

There is no animation loop and no timer: the app only asks for a repaint when
the grid or the input line actually changes, so an idle terminal costs nothing.

The prompt is `[user@host pwd] $` (`#` for root) and is written as one plain
run, so phones that fall back to a proportional font for `monospace` do not
open a gap between the user, `@`, the host and the path; an executed line
echoes the prompt and the command in the same run. When the full prompt does
not fit, it compacts to `[pwd] $` and finally to a truncated `[..tail] $`.

## Limits

- Output is captured when the command finishes, not streamed line by line, so
  a long-running command does not repaint until it returns. Output is capped at
  16 KB (`[output truncated]`): the scrollback only keeps 200 rows, so parsing
  more text than that would only burn CPU on the phone.
- There is no wide-character cell model: every Unicode code unit occupies one
  monospace cell.
- Scrollback defaults to 200 rows (`scrollback = n` in `cterm.run`/`cterm.new`).
- The font can be changed at runtime with the **Font** command; changing it
  reflows the grid and keeps the bottom rows.

## Tests

`tests/lua/check_modules.lua` loads the app in module mode and checks the ANSI
parser, wrapping, scrollback, line editing and one real headless canvas frame
(including a captured `echo`), plus the phone behaviour: Up/Down scrolling, the
grid revision not changing while typing, FIRE opening the text box in `123`
mode, FIRE running the line on the background thread in `abc` mode, and the
prompt fitting on a narrow canvas. The row model has its own block: exact row
width, one run per colour change, adjacent same-style writes merging, the
attribute bits, erase/insert/delete/resize updating text and runs together,
escapes split across two writes, and coloured lines staying at a handful of
runs after scrolling. `tests/lua/run_modules.sh` stages the app as
`cterm-app.lua` in the same headless shim run as the rock payloads.
