Fixes#1183
I'm not sure why the original logic was there. When I translated the
original test cases to shell and ran them in xterm, they did NOT match
what Ghostty expected. This updates the tests to match xterm and removes
this case. We can add back in more tests if/when we figure out under
what scenario this original logic was correct (if it ever was).
Fixes#1159
The cursor position is an "active" coordinate (defined at the top of
Screen.zig), but our resize was treating it as a "viewport" coordinate.
This adds a new configuration `grapheme-width-method` to change the
default behavior that Ghostty uses to calculate grapheme width. The
default value is `unicode` which is identical to setting mode 2027.
**IMPORTANT:** This changes the default Ghostty behavior to be fully
grapheme-aware including ZWJs, VS15, VS16. This may cause issues with
some legacy programs and shells.
I've changed my mind that this should become the default because enough
people use emojis now that I've found in the beta program there are more
issues reported about "incorrect emoji width" than any possibly desync
issues. We'll see.
For legacy programs, this can still be set to `grapheme-width-method =
wcswidth`.
Only SGR, DECSCUSR, DECSTBM, and DECSLRM are handled, as these are the
only ones that Ghostty supports (as far as I can tell) and are the only
ones that seem actually useful.
This fixes a couple of subtle rectangle select behaviors:
* Corrects how selection rolls over when crossing the x-boundary; this
was mentioned in #1021, this properly corrects it so both sides of the
x-boundary do not share characters.
* Corrects a minor quirk in the selection of initial cells in a
selection - this can be more readily observed when selecting a single
line with rectangle select. To correct this, we only use the x axis
when calculating this instead of both x and y.
This fixes an issue where selections from the bottom-right to the
top-left (or top-left to bottom-right), in addition to some single-line
rectangle selections, were not working.
This works by handling situations where only one of the x or y
axes in the start or end points may need to be flipped to get the
correct top-left or bottom-right of a selection. We call these kinds of
orientations "mirrored", like you were looking in a mirror.
This also adds a small bit of logic that keeps these kinds of motions in
rectangle selection from selecting the character before or after it.
This has the current side-effect of anchoring a rectangle selection to
the original characters if you change directions during the selection,
something I will look at in a later commit.
Finally, this also removes rectangle select on double-click. I thought
this might be a good idea, but word select in rectangle mode really
does not work (the effect seems pretty erratic), and it's not
implemented in Kitty either.
Fixes#1008.
Implement handling of mode 1047, which enters the alternate screen. This
is not used often, typically applications will favor 1049 (enter alt
screen, save cursor, clear alt screen).
This adds rectangle select mode; when dragging with ctrl+alt (or
super+alt on MacOS), this allows you to select a rectangular region of
the terminal instead of the full start-end points of the buffer.
Fixes#906
This changes our resize behavior when increasing row height.
If the cursor was originally at the bottom of the viewport, existing
scrollback (if it exists) will be "pulled down" from the top,
effectively keeping the cursor at the bottom. This is the behavior
today, prior to this commit.
If the cursor is not at the bottom of the viewport, scrollback will NOT
be "pulled down" and instead blank lines will be added _below_. This is
new behavior.
Fixes#906
Previously, when the cursor isn't at the bottom and you resized to less
cols, the cursor would jump to the bottom of the viewport. But if you
resized to more columns it didn't do this. This was jarring. This commit
attempts to keep the cursor at the same place.
Fixes#741
This completely reimplements double-click-and-drag logic for selecting
by word. The previous implementation was horribly broken. See #741 for
all the details.
The implemented logic now is:
* A double-click initiates a select-by-word selection mechanism.
- A double-click may start on a word or whitespace
- If the initial double-click is on a word, that word is immediately selected.
- If the initial double-click is on whitespace, the whitespace is not selected.
* A "word" is determined by a non-boundary character meeting a boundary character.
- A boundary character is `NUL` ` ` (space) `\t` `'` `"`
- This list is somewhat arbitrary to make the terminal "feel" good.
- Cell SGR states (fg/bg, bold, italic, etc.) have no effect on boundary determination or selection logic.
* As the user drags _on the same line_:
- No selection change occurs until the cursor is over a new word. Whitespace change does nothing.
- When selection is over a new word, that entire word added to the selection.
* When the user drags _up_ one or more lines:
- If the cursor is over whitespace, all lines from the selection point up to but not including the cursor line are selected.
* This selection is done in accordance to the previous rules.
- If the cursor is over a word, the word becomes the beginning of the selection.
- The end of the selection in all cases is the first word at or before the initial double-click point.
* When the user drags _down_ one or more lines:
- The same logic as _up_ but swap the "beginning" and "end" of selection terminology.
* With this logic, the behavior of Ghostty has the following invariants:
- Whitespace is never selected unless it is between two selected words
- Selection implies at least one word is highlighted
- The initial double-click point marks the beginning or end of a selection, never the middle.
Normally, we queue all the writes we need from a single `read()` syscall
and only wake up the writer thread at the end of processing that batch
of data.
But under very heavy load or large batches of data, it is possible for the
terminal sequences to generate enough writes to the pty to fill the
writer thread queue while we're still processing the data from `read()`.
This modifies our queuer to attempt to queue, but if the queue is full
we wake up the writer thread immediately then queue again (which should
succeed in every case -- eventually).