aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-17 14:02:54 +0200
committeruser <user@clank>2026-08-17 14:02:54 +0200
commitd90aadfc636cf6b655ed5e5488944b5385b451ec (patch)
tree0d5504febde4e13087610b2e81c2616c3fba5fa8 /src
parentsl0ppty: double-click a word to select it (diff)
downloadslosh-d90aadfc636cf6b655ed5e5488944b5385b451ec.tar.gz
slosh-d90aadfc636cf6b655ed5e5488944b5385b451ec.tar.xz
slosh-d90aadfc636cf6b655ed5e5488944b5385b451ec.zip
sl0ppty: a wrapped line is one line when you copy it
Select a line too long for the pane and the clipboard came back with a `\n` in the middle of it, at whatever column the pane happened to end. Paste that into a shell and it runs half a path. `pane_selection_text` set `trim` and left `unwrap` at whatever a zeroed struct gives, which is false. lib-vt's header names the exact trio the clipboard wants -- plain output with `unwrap` and `trim` both true, matching Ghostty's own `Screen.selectionString()` -- and we had two of the three. A row boundary in the grid is not a newline in the text: a long line occupies two rows and is still one line, and where it breaks is a fact about how wide the pane is rather than about what was copied. Hard newlines are untouched, which is the half a fix for this could easily have broken: lib-vt joins only the rows that are continuations, so three separate lines still come back as three lines. Both directions are now asserted, because "copy joins everything" would be a worse bug than the one being fixed and would look identical from the inside. `emit` is now set explicitly as well, to `GHOSTTY_FORMATTER_FORMAT_PLAIN`. It is already zero, so this changes nothing today -- but this is the API where a zeroed field means "whatever the enum happens to start with" (DESIGN.md's note on `ghostty_terminal_set`), and a silently defaulted field is how the wrap flag came to be missing. There is one call site, checked. Verified: three checks in `tests/test_clipboard.py` -- a 50-character line printed into a 40-column pane, dragged across the row boundary, arrives as the string that was printed with no newline in it; three short lines keep their two newlines; and a double-click on a word in a wrapped line brings back no newline either. 46 checks in that file, 1795 overall, all green.
Diffstat (limited to 'src')
-rw-r--r--src/pane.c16
1 files changed, 16 insertions, 0 deletions
diff --git a/src/pane.c b/src/pane.c
index 9f39a0b..860806f 100644
--- a/src/pane.c
+++ b/src/pane.c
@@ -1026,6 +1026,22 @@ bool pane_select_word(pane_t *p, uint16_t x, uint16_t y, const char *seps) {
char *pane_selection_text(pane_t *p) {
GhosttyTerminalSelectionFormatOptions opts =
GHOSTTY_INIT_SIZED(GhosttyTerminalSelectionFormatOptions);
+ /* Plain text, and both flags on: lib-vt documents this exact trio as the
+ * clipboard behaviour, and getting it wrong is invisible until somebody pastes.
+ *
+ * `unwrap` is the one that matters. A row boundary in the grid is not a
+ * newline in the text -- a line too long for the pane occupies two rows and is
+ * still one line -- so without this, copying a wrapped path or a wrapped URL
+ * pasted a `\n` into the middle of it. The newline came from how wide the pane
+ * happened to be, which is not a fact about what was copied. Hard newlines are
+ * untouched: lib-vt joins only the rows that are continuations.
+ *
+ * `emit` is set rather than left zero even though PLAIN *is* zero. This is the
+ * API where a zero means "whatever the enum happens to start with" (see
+ * DESIGN.md on ghostty_terminal_set), and a silent default is how the wrap flag
+ * came to be missing in the first place. */
+ opts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN;
+ opts.unwrap = true;
opts.trim = true;
uint8_t *ptr = NULL;
size_t len = 0;