Skip to content

Commit

Permalink
more vt stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchellh committed Dec 18, 2024
1 parent eec1e42 commit 5687df3
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 5 deletions.
62 changes: 62 additions & 0 deletions docs/vt/concepts/cursor.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: Cursor
description: |-
The cursor is the row and column where the next
character will be printed or location-sensitive control
sequence will be executed.
---

The cursor is always present in a terminal and is located at
some row and column within the active screen area. The cursor
may be visually hidden but the terminal internal state always
has a cursor and it is always located at some active position.

The cursor most commonly is associated with where the next
printed character will be placed. However, the cursor is also
used for location-sensitive control sequences. For example,
when an [erase line control sequence](/docs/vt/csi/el) is
executed, the cursor determines the first line to erase.

The terminal has a single cursor [per screen](/docs/concepts/screen).
**Note that this document is about the cursor as it relates
to the [terminal API](/docs/vt).** Applications such as editors may
have their own concept known as a "cursor" that is completely
unrelated to the terminal cursor. For example, an editor may support
"multiple cursors", but the underlying terminal API is both a separate
concept and only supports a single cursor at any given moment.

## Initial State

The cursor is always initially located at the top-left corner of the screen.

## Pending Wrap State

The pending wrap state is a boolean value that is set when a character
is printed in the rightmost column of the screen to indicate that the
next printed character should wrap to the next line.

If the pending wrap state is set, the next printed character will
move the cursor to the leftmost column of the next line, unset
the pending wrap state, and then print the character[^1].

The pending wrap state may feel like an obvious and inconsequential
feature, but it has a significant (but subtle) impact on cursor
behavior. For example, print followed by [backspace](/docs/vt/control/bs)
behaves differently depending on if you're printing in the rightmost
column of the screen or not.

If you print a character in any column other than the rightmost column
and then send a [backspace](/docs/vt/control/bs) control character, the
cursor will move back on top of the most recently printed character.
But if you print a character in the rightmost column and then send a
[backspace](/docs/vt/control/bs) control character, the cursor will move
to the left of the character most recently printed. This is the
source of [bugs in multiple popular shell prompts](https://github.com/ghostty-org/ghostty/issues/884).

You will see that many control sequences note that they
"unset the pending wrap state". This is just as it sounds: if the
pending wrap state is set on the cursor, it becomes unset. The next
printed character will not wrap to the next line.

[^1]: This isn't strictly true. Wraparound modes and scroll regions
can change this behavior.
6 changes: 6 additions & 0 deletions docs/vt/concepts/screen.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Screen
description: TODO
---

TODO
17 changes: 15 additions & 2 deletions docs/vt/bel.mdx → docs/vt/control/bel.mdx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
---
title: Bell (BEL)
title: Bell (BEL) (Unimplemented)
description: Raise the attention of the user.
---

<VTSequence sequence="BEL" unimplemented />

The purpose of the bell sequence is to raise the attention
of the user. Historically, this would
of the user.

Historically, this would
[ring a physical bell](https://en.wikipedia.org/wiki/Bell_character).
Today, many alternate behaviors are acceptable:

Expand All @@ -25,4 +27,15 @@ OSC sequences, although `ST` is preferred. If `BEL` is the
terminating character for an OSC sequence, any responses should
also terminate with the `BEL` character.[^1]

## Ghostty Status

This control character is not currently implemented in Ghostty,
but can be used as an OSC terminator. If it is used as an OSC
terminator, Ghostty will terminate any responses with the
`BEL` character.

There is an [open discussion](https://github.com/ghostty-org/ghostty/discussions/2710)
about implementing the bell character in Ghostty and what
that will look like.

[^1]: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
12 changes: 12 additions & 0 deletions docs/vt/control/bs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: Backspace (BS)
description: Move the cursor backward one position.
---

# Backspace (BS)

<VTSequence sequence="BS" />

This sequence performs [cursor backward (CUB)](/docs/vt/csi/cub)
with `Pn = 1`. There is no additional or different behavior for
using `BS`.
181 changes: 181 additions & 0 deletions docs/vt/csi/cub.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
---
title: Cursor Backward (CUB)
description: Move the cursor left `n` cells.
---

<VTSequence sequence={["CSI", "Pn", "D"]} />

The parameter `n` must be an integer greater than or equal to 1. If `n` is less than
or equal to 0, adjust `n` to be 1. If `n` is omitted, `n` defaults to 1.

This sequence always unsets the
[pending wrap state](/docs/vt/concepts/cursor#pending-wrap-state).

The leftmost boundary the cursor can move to is determined by the current
cursor column and the [left margin](#TODO). If the cursor begins to the left of the left margin, modify the left margin to be the leftmost column
for the duration of the sequence. The leftmost column the cursor can be on
is the left margin.

With the above in place, there are three different cursor backward behaviors
depending on the mode state of the terminal. The possible behaviors are listed
below. In the case of a conflict, the top-most behavior takes priority.

- **Extended reverse wrap**: [wraparound (mode 7)](#TODO) and [extended reverse wrap (mode 1045)](#TODO)
are **BOTH** enabled
- **Reverse wrap**: [wraparound (mode 7)](#TODO) and [reverse wrap (mode 45)](#TODO)
are **BOTH** enabled
- **No wrap**: The default behavior if the above wrapping behaviors
do not have their conditions met.

For the **no wrap** behavior, move the cursor to the left `n` cells while
respecting the aforementioned leftmost boundary. Upon reaching the leftmost
boundary, stop moving the cursor left regardless of the remaining value of `n`.
The cursor row remains unchanged.

For the **extended reverse wrap** behavior, move the cursor to the left `n`
cells while respecting the aforementioned leftmost boundary. Upon reaching the
leftmost boundary, if `n > 0` then move the cursor to the [right margin](#TODO)
of the line above the cursor. If the cursor is already on the
[top margin](#TODO), move the cursor to the right margin of the
[bottom margin](#TODO). Both the cursor column and row can change in this
mode. Compared to non-extended reverse wrap, the two critical differences are
that extended reverse wrap doesn't require the previous line to be wrapped
and extended reverse wrap will wrap around to the bottom margin.

For the **reverse wrap** (non-extended) behavior, move the cursor to the left `n`
cells while respecting the aforementioned leftmost boundary. Upon reaching the
leftmost boundary, if `n > 0` and the previous line was wrapped, then move the
cursor to the [right margin](#TODO) of the line above the cursor. If the previous
line was not wrapped, the cursor left operation is complete even if there
is a remaining value of `n`. If the cursor
is already on the [top margin](#TODO), do not move the cursor up.
This wrapping mode does not wrap the cursor row back to the bottom margin.

For **extended reverse wrap** or **reverse wrap** modes, if the pending
wrap state is set, decrease `n` by 1. In these modes, the initial cursor
backward count is consumed by the pending wrap state, as if you pressed
"backspace" on an empty newline and the cursor moved back to the previous line.

## Validation

### CUB V-1: Pending Wrap is Unset

```bash
cols=$(tput cols)
printf "\033[${cols}G" # move to last column
printf "A" # set pending wrap state
printf "\033[D" # move back one
printf "XYZ"
```

```
|________XY|
|Zc________|
```

### CUB V-2: Leftmost Boundary with Reverse Wrap Disabled

```bash
printf "\033[?45l" # disable reverse wrap
echo "A"
printf "\033[10D" # back
printf "B"
```

```
|A_________|
|Bc________|
```

### CUB V-3: Reverse Wrap

```bash
cols=$(tput cols)
printf "\033[?7h" # enable wraparound
printf "\033[?45h" # enable reverse wrap
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[${cols}G" # move to end of line
printf "AB" # write and wrap
printf "\033[D" # move back two
printf "X"
```

```
|_________Xc
|B_________|
```

### CUB V-4: Extended Reverse Wrap Single Line

```bash
printf "\033[?7h" # enable wraparound
printf "\033[?1045h" # enable extended reverse wrap
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
echo "A"
printf "B"
printf "\033[2D" # move back two
printf "X"
```

```
|A________Xc
|B_________|
```

### CUB V-5: Extended Reverse Wrap Wraps to Bottom

```bash
cols=$(tput cols)
printf "\033[?7h" # enable wraparound
printf "\033[?1045h" # enable extended reverse wrap
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[1;3r" # set scrolling region
echo "A"
printf "B"
printf "\033[D" # move back one
printf "\033[${cols}D" # move back entire width
printf "\033[D" # move back one
printf "X"
```

```
|A_________|
|B_________|
|_________Xc
```

### CUB V-6: Reverse Wrap Outside of Margins

```bash
printf "\033[1;1H"
printf "\033[0J"
printf "\033[?45h"
printf "\033[3r"
printf "\b"
printf "X"
```

```
|__________|
|__________|
|Xc________|
```

### CUB V-7: Reverse Wrap with Pending Wrap State

```bash
cols=$(tput cols)
printf "\033[?45h"
printf "\033[${cols}G"
printf "\033[4D"
printf "ABCDE"
printf "\033[D"
printf "X"
```

```
|_____ABCDX|
```
29 changes: 26 additions & 3 deletions docs/vt/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ description: |-
Programs running in terminals use **control sequences** to interact with the
terminal. Control sequences are a series of special characters that the terminal
interprets as commands. This is sometimes referred as **VT sequences** or
**escape codes**. This section will document all of the control sequences that
Ghostty supports.
**escape codes**.

Control sequences are typically poorly specified, so this section
serves both as a reference of what Ghostty supports as well as
a possible reference for other terminal emulators.

## Control Characters

Expand All @@ -18,4 +21,24 @@ meaning. They have no encoding format; you send the bytes as-is.

| Name | Byte | Description |
|------|------|-------------|
| [BEL](/docs/vt/bel) | `0x07` | Alert the user (beep) |
| [BEL](/docs/vt/control/bel) | `0x07` | Alert the user (beep) |
| [BS](/docs/vt/control/bs) | `0x08` | Move cursor backward one position |

## CSI Sequences

CSI sequences are in the format below. An example CSI sequence
is `ESC [ 1 ; 2 m`. Below the syntax is a table of supported
CSI sequences in Ghostty.

```
csi_sequence = "0x1B" "[" params intermediates final
params = param | param sep params
param = [0-9]
sep = ";" | ":"
intermediates = [0x20-0x2F]*
final = [0x40-0x7E]
```

| Name | Sequence | Description |
|------|----------|-------------|
| [CUB](/docs/vt/csi/cub) | `CSI Pn D` | Move cursor left |

0 comments on commit 5687df3

Please sign in to comment.