T59: a shell whose prompt says click_events, as fish from 4.1 does, hears the click as an SGR press and moves its own cursor, since fish moves by code point and its redraws drop the wrap flags a count follows; a press let go in another cell, or released while the program tracks the mouse, moves nothing

This commit is contained in:
ZacharyZhang-NY committed 2026-10-19 15:34:17 -04:00
1 parent 82cfe91810
commit 0c55d41026
12 files changed
+242 -46

No files matched your search

+1 -1
View File
@@ -114,4 +114,4 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
- 2026-10-19: a drag moves Ely along the edge she stands on, which the owner asked for after 0.1.5. On macOS, Windows and X11 her glue moves her window after the pointer's place on the screen, `NSEvent`'s `mouseLocation`, `GetCursorPos` and `query_pointer`, since her window moves under the pointer, so a place in it would lag; her window keeps the pointer while the button is held, inside it or not, as AppKit, gpui's `SetCapture` and X's implicit grab give it. A Wayland popup cannot move, so there she moves once let go: the settings keep the place, and the twice-a-second check opens her popup there. She moves by whole points inside the pane, and `ely_left` keeps where she stands, 140 by default, where she stood before; a narrower pane holds her at its right end, and the log names where she is let go. Under Xorg's dummy screen xdotool dragged her 200 pixels and her window followed them; under Sway nested in Xorg, whose X input gives its seat a pointer, xdotool's drag reopened her popup 200 points on. Sway's own `seat cursor` commands send no motion while their press is held, and Sway draws no popup opened before its window settled, so a probe there reopens her before it drags.
- 2026-10-19: Settings › Terminal checks for updates, which the owner asked for after 0.1.5: under Ely, a switch, on by default, checks at start and once a day, and Check now checks at once. The check runs `curl`, as git runs through its command line, so no HTTP or TLS crate joins the build; macOS and Windows 10 ship it, and a missing curl shows as a failed check. It asks Gitea's API for the latest published release, compares its tag's three numbers with the running version, and the row says what it found; a newer one brings Download, which opens the release's page in the browser, since replacing a running, signed app is a job of its own. An automatic check that finds one posts a system notification, at most once a day, whose press opens the page. Checks run off the main thread, one at a time, each start and finding logged; a failure shows its error in the row. Under Xorg's dummy screen the start's check found 0.1.5 the latest, Check now and the switch checked again, and a build named 0.1.4 showed 0.1.5 out with Download, which opened its page.
- 2026-10-19: the terminal shows a scrollbar, which a user asked for in issue #2 after 0.1.5: a program that tracks the mouse takes the wheel, so the wheel alone could not reach the history. A thumb rides the view's right edge, over the padding, in Ely's scrollbar sizes and colors, its thickness, its least length and the foreground at 22 and 42 percent; its length is the screen's share of the history, and its place where the view stands. A drag moves the view through the history, whatever mouse mode the program asked for; the bar covers the grid's hitbox, so the terminal sees none of its presses, and a release with no press of its own reports nothing. Ely's `Scrollbar` reads a gpui `ScrollHandle`, and the terminal's place is alacritty's display offset, so the bar is the terminal's own, drawn as Ely draws hers and dragged through gpui's drag as hers is; gpui sets a drag's anchor at its first move past the threshold, so a pointer that jumps far at once loses that jump. The alternate screen holds no history, so it shows none. Under Xorg's dummy screen, 500 lines in, a drag to the top showed the first prompt and lines 1 to 39, and with mouse tracking on, which sent the wheel to the program, the bar still scrolled.
- 2026-10-19: a plain click on the line the shell reads moves its cursor there, which a user asked for after 0.1.6, as Ghostty's `cursor-click-to-move`: a press with no modifier, one click, that stays in its cell and leaves no selection sends Left or Right arrows, one per character, each pressed and let go as the keyboard would, through the key encoder, so DECCKM, kitty's flags and win32-input-mode shape them. It runs only while OSC 133 says the shell reads a line, B since the last A or C, and only when the cursor's line, soft wraps joined, shows the prompt's cells before the cursor, which the prompt's link marks, and the click lies past them; so bash 3.2, which sends no C, moves nothing once its command left the prompt's line. Cells after the last character typed count only up to the cursor, so a click past the end goes to the end; a wide character takes one arrow. Never on the alternate screen or while the program tracks the mouse, whose press it hears instead. One line only, as Ghostty's `cl=line`: a continuation line of a multi-line command is another line. A right prompt or an autosuggestion past the typed text counts as text, as in Ghostty, so a click on it may take the suggestion.
- 2026-10-19: a plain click on the line the shell reads moves its cursor there, which a user asked for after 0.1.6, as Ghostty's `cursor-click-to-move`: a press with no modifier, one click, let go in its own cell with no selection, while OSC 133 says the shell reads a line, B since the last A or C, and the program tracks no mouse at the release. A shell whose prompt start says `click_events=1` or `2`, as fish from 4.1 says on its own, hears the click itself, a left press in SGR, its row the screen's or counted from the prompt's first, as kitty and Ghostty send it, and moves its own cursor, combining marks and its own redraws included: fish moves by code point, and its redraws drop the wrap flags a count would follow. Any other shell gets Left or Right arrows, one per cell, each pressed and let go as the keyboard would, through the key encoder, so DECCKM, kitty's flags and win32-input-mode shape them; the count runs only on the cursor's line, soft wraps joined, past the prompt's cells, which the prompt's link marks, so bash 3.2, which sends no C, moves nothing once its command left the prompt's line. Cells after the last character typed count only up to the cursor, so a click past the end goes to the end; a wide character takes one arrow. Never on the alternate screen. One line only, as Ghostty's `cl=line`. A right prompt or an autosuggestion past the typed text counts as text, as in Ghostty, so a click on it may take the suggestion. The first click of a double click moves the cursor before the word selects, as in Ghostty.
+1 -1
View File
@@ -57,7 +57,7 @@ Ghostty is the bar for what the terminal does. ELYTerminal owns its engine on `a
- Keys: xterm encoding with modifiers, application cursor mode, Option as Alt on macOS, the kitty keyboard protocol as the program sets its flags; a key Shift makes a symbol of goes as its own key with Shift, the symbol its alternate, as Ghostty sends it, on macOS and Windows. On Windows keys go as win32-input-mode records while ConPTY asks, as Windows Terminal sends them. Keypad keys type their characters, as Ghostty's do with mode 1035 on by default.
- IME: marked text drawn at the cursor; committed text sent to the program.
- Mouse: select by character, word (double press) and line (triple press); Shift extends; mouse reporting (X10, normal, button, any; SGR and UTF-8); wheel scrolls back, or reports, or sends arrows on the alternate screen.
- Click to move: while the shell reads a line, as its OSC 133 marks say, a plain click on that line, past its prompt, moves the shell's cursor there with arrow keys, as Ghostty's `cursor-click-to-move`.
- Click to move: while the shell reads a line, as its OSC 133 marks say, a plain click on that line, past its prompt, moves the shell's cursor there, as Ghostty's `cursor-click-to-move`: a shell that takes clicks, as fish does, hears the click; others get arrow keys.
- Clipboard: copy, paste (bracketed when asked), copy on select per setting, OSC 52 writes.
- Links: OSC 8 and plain URLs; Cmd-press (Ctrl-press off macOS) opens them. Inside herdr, while it takes the mouse, herdr opens them, web links only, as herdr does, whole where its pane wraps them, and underlines them while the key is held; with Shift the terminal opens what it sees.
- Focus reporting, title (OSC 0/2), scrollback limit per setting, find in scrollback (Ely's `FindWidget`).
+66 -4
View File
@@ -1,4 +1,4 @@
//! A click on the line the shell reads moves its cursor there with arrows, as Ghostty's `cursor-click-to-move`.
//! A click on the line the shell reads moves its cursor there, as Ghostty's `cursor-click-to-move`: arrows, or the press itself where the shell takes clicks.
use alacritty_terminal::{
grid::Dimensions,
@@ -10,7 +10,34 @@ use alacritty_terminal::{
};
use gpui::Context;
use super::{prompts::prompted, view::TerminalView};
use super::{
marks::Click,
mouse::Tracking,
prompts::{prompted, start},
view::TerminalView,
};
/// Where the shell stands, as its OSC 133 marks say.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Shell {
/// A command runs, or nothing said otherwise.
#[default]
Busy,
/// Its prompt draws; the line after it takes a click so.
Prompting(Click),
/// It reads a line, which takes a click so.
Reading(Click),
}
impl Shell {
/// The shell after B: it reads the line its prompt started, which takes arrows without one.
pub fn reads(self) -> Self {
match self {
Self::Prompting(click) | Self::Reading(click) => Self::Reading(click),
Self::Busy => Self::Reading(Click::Arrows),
}
}
}
/// Arrows from the cursor to `click`, right positive; none when `click` lies off the input after the cursor's prompt, soft wraps joined.
pub fn arrows<T>(term: &Term<T>, click: Point) -> Option<i32> {
@@ -65,13 +92,48 @@ pub fn arrows<T>(term: &Term<T>, click: Point) -> Option<i32> {
})
}
/// The left press, SGR, a shell that takes clicks hears for `click`, its row the screen's or counted from its prompt's first, as kitty and Ghostty send it; none before that prompt.
pub fn press<T>(term: &Term<T>, click: Point, relative: bool) -> Option<Vec<u8>> {
if term.mode().contains(TermMode::ALT_SCREEN) {
return None;
}
let first = start(term, term.grid().cursor.point.line.0)?;
let opens = (0..term.columns())
.position(|column| prompted(&term.grid()[Line(first)][Column(column)]))
.expect("a prompt's first row holds its cells");
if (click.line.0, click.column.0) < (first, opens) {
return None;
}
let row = if relative {
click.line.0 - first
} else {
click.line.0
} + 1;
if row < 1 {
return None;
}
Some(format!("\x1b[<0;{};{row}M", click.column.0 + 1).into_bytes())
}
impl TerminalView {
/// Moves the shell's cursor to `click`, a press and release of an arrow per character, while the shell reads a line.
/// Moves the shell's cursor to `click` while it reads a line: arrows, each pressed and let go, or the press where the shell takes clicks.
pub(super) fn click_to_move(&mut self, click: Point, cx: &mut Context<Self>) {
let pid = self.pty.pid;
if !self.reading {
let Shell::Reading(takes) = self.shell else {
log::debug!("terminal {pid}: a click at {click:?}, no line read");
return;
};
if self.mouse.tracking != Tracking::Off {
log::debug!("terminal {pid}: a click at {click:?}, the program tracks the mouse");
return;
}
if takes != Click::Arrows {
let Some(bytes) = press(&self.pty.term.lock(), click, takes == Click::Relative) else {
log::debug!("terminal {pid}: a click at {click:?}, before the prompt");
return;
};
log::debug!("terminal {pid}: a click at {click:?} goes to the shell as {bytes:?}");
return self.input(bytes, cx);
}
let Some(count) = arrows(&self.pty.term.lock(), click) else {
log::debug!("terminal {pid}: a click at {click:?}, off the input");
+61
View File
@@ -89,3 +89,64 @@ fn a_prompt_over_two_lines_keeps_its_input_on_the_last() {
assert_eq!(arrows(&two, click(1, 2)), Some(-4));
assert_eq!(arrows(&two, click(0, 3)), None, "the prompt's first line");
}
/// The press `term` sends for a click at `at`, as text.
fn sent(term: &Term<VoidListener>, at: Point, relative: bool) -> Option<String> {
press(term, at, relative).map(|bytes| String::from_utf8(bytes).expect("ASCII"))
}
#[test]
fn a_shell_that_takes_clicks_hears_the_press_where_it_landed() {
let marked = "\x1b]133;A;click_events=1\x07$ \x1b]133;B\x07";
let typed = term(20, &format!("out\r\n{marked}echo hi"));
assert_eq!(
sent(&typed, click(1, 7), false).as_deref(),
Some("\x1b[<0;8;2M")
);
assert_eq!(
sent(&typed, click(1, 7), true).as_deref(),
Some("\x1b[<0;8;1M")
);
let below = sent(&typed, click(2, 3), true);
assert_eq!(
below.as_deref(),
Some("\x1b[<0;4;2M"),
"a row below goes too"
);
assert_eq!(sent(&typed, click(0, 1), false), None, "before the prompt");
let two = term(
20,
"\x1b]133;A;click_events=2\x07~/src\r\n> \x1b]133;B\x07ls",
);
let relative = sent(&two, click(1, 3), true);
assert_eq!(
relative.as_deref(),
Some("\x1b[<0;4;2M"),
"from the prompt's first row"
);
let full = term(20, &format!("{marked}vim\x1b[?1049h"));
assert_eq!(
sent(&full, click(0, 3), false),
None,
"the alternate screen"
);
let scrolled = term(20, &format!("{marked}a\r\nb\r\nc\r\nd\r\ne"));
assert_eq!(
sent(&scrolled, click(-1, 0), false),
None,
"a row in the history"
);
}
#[test]
fn input_reads_the_line_its_prompt_started() {
assert_eq!(Shell::Busy.reads(), Shell::Reading(Click::Arrows));
assert_eq!(
Shell::Prompting(Click::Absolute).reads(),
Shell::Reading(Click::Absolute)
);
assert_eq!(
Shell::Reading(Click::Relative).reads(),
Shell::Reading(Click::Relative)
);
}
+45 -6
View File
@@ -1,8 +1,8 @@
/// A semantic prompt mark a program sends, OSC 133.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mark {
/// A prompt starts: A, or N.
Prompt,
/// A prompt starts: A, or N, saying how its line takes a click.
Prompt(Click),
/// The prompt ends and input starts: B, or I.
Input,
/// Input ends and the command's output starts: C.
@@ -11,6 +11,17 @@ pub enum Mark {
Done(Option<i32>),
}
/// How the line a prompt reads takes a click, as its start's `click_events` says.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Click {
/// Arrows the terminal counts.
Arrows,
/// A mouse press at the click's place on the screen, `click_events=1`.
Absolute,
/// A mouse press, its row counted from the prompt's first, `click_events=2`.
Relative,
}
/// The mark in an OSC 133 body past `133;`; none for one the terminal takes no action on.
pub(crate) fn read(body: &[u8]) -> Option<Mark> {
let shown = || String::from_utf8_lossy(body);
@@ -27,7 +38,7 @@ pub(crate) fn read(body: &[u8]) -> Option<Mark> {
}
};
match letter {
b'A' | b'N' => Some(Mark::Prompt),
b'A' | b'N' => Some(Mark::Prompt(click(options))),
b'B' | b'I' => Some(Mark::Input),
b'C' => Some(Mark::Output),
b'D' => Some(Mark::Done(status(options))),
@@ -43,6 +54,25 @@ pub(crate) fn read(body: &[u8]) -> Option<Mark> {
}
}
/// How A's options say its line takes a click; arrows without `click_events`.
fn click(options: &[u8]) -> Click {
let value = options
.split(|&byte| byte == b';')
.find_map(|option| option.strip_prefix(b"click_events="));
match value {
None | Some(b"0") => Click::Arrows,
Some(b"1") => Click::Absolute,
Some(b"2") => Click::Relative,
Some(other) => {
log::warn!(
"pty: OSC 133;A with click_events={}, no such mode, so arrows",
String::from_utf8_lossy(other)
);
Click::Arrows
}
}
}
/// D's status, its first option; none when the program gave none or no number.
fn status(options: &[u8]) -> Option<i32> {
let first = options.split(|&byte| byte == b';').next()?;
@@ -67,9 +97,18 @@ mod tests {
#[test]
fn each_mark_reads_with_its_options_as_ghostty_reads_it() {
assert_eq!(read(b"A"), Some(Mark::Prompt));
assert_eq!(read(b"A;click_events=1"), Some(Mark::Prompt));
assert_eq!(read(b"N;aid=7"), Some(Mark::Prompt));
assert_eq!(read(b"A"), Some(Mark::Prompt(Click::Arrows)));
assert_eq!(
read(b"A;click_events=1"),
Some(Mark::Prompt(Click::Absolute))
);
assert_eq!(
read(b"A;aid=3;click_events=2"),
Some(Mark::Prompt(Click::Relative))
);
assert_eq!(read(b"A;click_events=0"), Some(Mark::Prompt(Click::Arrows)));
assert_eq!(read(b"A;cl=line"), Some(Mark::Prompt(Click::Arrows)));
assert_eq!(read(b"N;aid=7"), Some(Mark::Prompt(Click::Arrows)));
assert_eq!(read(b"B"), Some(Mark::Input));
assert_eq!(read(b"I"), Some(Mark::Input));
assert_eq!(read(b"C;cmdline_url=false"), Some(Mark::Output));
+8 -3
View File
@@ -214,9 +214,14 @@ impl TerminalView {
) {
if event.button == MouseButton::Left {
self.end_selection(cx);
let click = self.pointer.click.take();
if let Some(click) = click.filter(|_| self.selected().is_none()) {
self.click_to_move(click, cx);
let (_, point, _) = self.cell_at(event.position);
match self.pointer.click.take() {
Some(click) if click != point => log::debug!(
"terminal {}: a press at {click:?} let go at {point:?}",
self.pty.pid
),
Some(click) if self.selected().is_none() => self.click_to_move(click, cx),
_ => {}
}
}
if let Some(released) = button(event.button) {
+20 -14
View File
@@ -285,26 +285,32 @@ fn prompt(row: &Row<Cell>) -> Option<u32> {
})
}
/// The first row of the nearest prompt at or above `line`, blank rows within it passed.
pub(super) fn start<T>(term: &Term<T>, line: i32) -> Option<i32> {
let first = term.topmost_line().0;
let number = |line: i32| prompt(&term.grid()[Line(line)]);
// ponytail: a scan of the rows on the way; index prompt rows if long histories lag.
let (found, own) = (first..=line)
.rev()
.find_map(|line| number(line).map(|own| (line, own)))?;
let mut start = found;
for above in (first..found).rev() {
match number(above) {
Some(other) if other != own => break,
Some(_) => start = above,
None => {}
}
}
Some(start)
}
/// The display offset that puts the prompt before or after the view's top at the top, as Ghostty jumps.
pub fn jump<T>(term: &Term<T>, up: bool) -> Option<usize> {
let top = -(term.grid().display_offset() as i32);
let (first, last) = (term.topmost_line().0, term.bottommost_line().0);
let number = |line: i32| prompt(&term.grid()[Line(line)]);
// ponytail: a scan of the rows on the way; index prompt rows if long histories lag.
let found = if up {
// The nearest prompt above the view's top, then its first row, blank rows within it passed.
let (line, own) = (first..top)
.rev()
.find_map(|line| number(line).map(|own| (line, own)))?;
let mut start = line;
for above in (first..line).rev() {
match number(above) {
Some(other) if other != own => break,
Some(_) => start = above,
None => {}
}
}
start
start(term, top - 1)?
} else {
// The prompt at or above the view's top may go on below it; the next one starts.
let here = (first..=top).rev().find_map(number);
+1 -1
View File
@@ -63,7 +63,7 @@ fn the_rewrite_reads_a_mark_as_the_session_hears_it() {
"A", "A;k=i", "N", "B", "I", "C;x", "D;1", "D;abc", "D", "", "AB", "L", "P;k=i", "Q", "a",
];
for body in bodies {
let heard = marks::read(body.as_bytes()).map(|mark| mark == Mark::Prompt);
let heard = marks::read(body.as_bytes()).map(|mark| matches!(mark, Mark::Prompt(_)));
assert_eq!(mark(body.as_bytes()), heard, "{body}");
}
}
+7 -1
View File
@@ -5,6 +5,7 @@ use alacritty_terminal::{
use futures::StreamExt;
use super::*;
use crate::term::marks::Click;
use crate::term::{
notify::Ask,
status::{Record, State},
@@ -115,7 +116,12 @@ fn osc_133_reaches_the_session_as_marks() {
"printf '\\033]133;A\\007>\\033]133;B\\007\\033]133;C\\033\\\\x\\033]133;D;3\\007'",
(40, 4),
);
let marks = [Mark::Prompt, Mark::Input, Mark::Output, Mark::Done(Some(3))];
let marks = [
Mark::Prompt(Click::Arrows),
Mark::Input,
Mark::Output,
Mark::Done(Some(3)),
];
assert_eq!(ran.marks, marks);
assert_eq!(ran.text, ">x");
assert_eq!(ran.prompt, [0], "the prompt's cell, not the output's");
+23 -7
View File
@@ -9,7 +9,11 @@ use futures::channel::mpsc::TryRecvError;
use super::*;
use crate::{
settings::Settings,
term::{Events, Pty, PtyEvent, config, marks::Mark, spawn},
term::{
Events, Pty, PtyEvent, config,
marks::{Click, Mark},
spawn,
},
};
/// A folder name with a space, a percent, a bell and a newline.
@@ -146,9 +150,9 @@ impl Run {
/// The prompt's folder, the script loaded by `probe`, then a cd into the inner folder.
fn walk(&mut self, probe: &str) {
let first = [Mark::Prompt, Mark::Input];
let first = [Mark::Prompt(Click::Arrows), Mark::Input];
self.until("the first prompt's marks", |run| {
run.marks.starts_with(&first)
run.marks.len() >= 2 && ends_like(&run.marks[..2], &first)
});
let start = self.start.clone();
self.send(probe);
@@ -173,7 +177,7 @@ impl Run {
let from = self.marks.len();
self.send(line);
self.until(&format!("the marks of {line}"), |run| {
run.marks[from..].ends_with(marks)
ends_like(&run.marks[from..], marks)
});
}
@@ -222,11 +226,22 @@ fn ended(status: i32) -> [Mark; 4] {
[
Mark::Output,
Mark::Done(Some(status)),
Mark::Prompt,
Mark::Prompt(Click::Arrows),
Mark::Input,
]
}
/// Whether `heard` ends as `wanted` does, a prompt's way of taking a click aside: fish from 4.1 takes clicks itself.
fn ends_like(heard: &[Mark], wanted: &[Mark]) -> bool {
let Some(start) = heard.len().checked_sub(wanted.len()) else {
return false;
};
heard[start..].iter().zip(wanted).all(|pair| match pair {
(Mark::Prompt(_), Mark::Prompt(_)) => true,
(heard, wanted) => heard == wanted,
})
}
#[test]
fn the_scripts_install_once_and_mend_a_changed_one() {
let dir = folder("install");
@@ -289,7 +304,8 @@ fn zsh_says_why_it_marks_no_prompt_without_prompt_percent() {
run.send("false");
let ended = [Mark::Output, Mark::Done(Some(1))];
run.until("the command's marks", |run| run.marks.ends_with(&ended));
assert!(!run.marks.contains(&Mark::Prompt), "{:?}", run.marks);
let prompted = run.marks.iter().any(|mark| matches!(mark, Mark::Prompt(_)));
assert!(!prompted, "{:?}", run.marks);
run.finish();
}
@@ -349,7 +365,7 @@ fn bash_says_why_it_marks_no_command_without_promptvars() {
run.shows("ELYTerminal: bash marks no command while promptvars is off")
});
run.walk("declare -F _ely_prompt >/dev/null && echo ELY-LOAD''ED");
run.ends("false", &[Mark::Prompt, Mark::Input]);
run.ends("false", &[Mark::Prompt(Click::Arrows), Mark::Input]);
run.finish();
}
+8 -7
View File
@@ -18,6 +18,7 @@ use gpui::{
};
use super::{
click::Shell,
find::Finder,
frame::frame,
ink::Ink,
@@ -85,8 +86,8 @@ pub struct TerminalView {
pub(super) ended: Option<i32>,
/// A command started, as OSC 133 C says, since the last one ended.
pub(super) ran: bool,
/// The shell reads a line: OSC 133 B came, and no A or C since.
pub(super) reading: bool,
/// Where the shell stands, as its OSC 133 marks say.
pub(super) shell: Shell,
/// The system notifications it posted.
pub(super) notices: Notices,
/// The program's title; empty while it names none.
@@ -148,7 +149,7 @@ impl TerminalView {
records: Records::default(),
ended: None,
ran: false,
reading: false,
shell: Shell::Busy,
notices: Notices::default(),
title: String::new(),
front: true,
@@ -276,13 +277,13 @@ impl TerminalView {
PtyEvent::Win32(on) => self.win32 = on,
PtyEvent::Mark(Mark::Done(status)) => self.finished(status, cx),
// A new prompt ends what was working, waiting or idle; done and error stay.
PtyEvent::Mark(Mark::Prompt) => {
self.reading = false;
PtyEvent::Mark(Mark::Prompt(click)) => {
self.shell = Shell::Prompting(click);
self.records.prompt();
self.restate(cx);
}
PtyEvent::Mark(Mark::Output) => (self.ran, self.reading) = (true, false),
PtyEvent::Mark(Mark::Input) => self.reading = true,
PtyEvent::Mark(Mark::Output) => (self.ran, self.shell) = (true, Shell::Busy),
PtyEvent::Mark(Mark::Input) => self.shell = self.shell.reads(),
PtyEvent::Status(status) => {
self.status(status);
self.restate(cx);
+1 -1
View File
@@ -71,7 +71,7 @@ What no check reached: no real KWin drew the blur; of Wayland desktops, only Hyp
| OSC 8 and plain links on Cmd-press, Ctrl-press off macOS; inside herdr, herdr opens them whole | T09, T45 | `term::links` tests; on macOS with real herdr 0.9.5 and a stand-in `open` on its PATH, Cmd-press on a URL herdr wraps, on its wrapped tail, on an OSC 8 link and on a URL over three rows opened the whole URL and the OSC 8 target, where before the app opened `.../ELYTermin` and nothing; Cmd-hover underlined the long URL across its rows; outside herdr the app opened the URL itself; an OSC 8 link whose label holds `file:///etc/hosts` and whose target is a web page opened the page through herdr, which keeps the target from the terminal while it takes the mouse; Shift-Cmd-press on a plain `file:///etc/hosts` inside herdr the app opened itself, caught by an `NSWorkspace` stand-in | Pass |
| Focus reporting, title, scrollback limit, find | T10 | `term::find` tests; round 3 failed at the cap, its fix stands on a red-green test | Pass |
| A scrollbar over the history: a drag on its thumb scrolls, whatever the program does with the wheel | T58 | `term::scrollbar` test: the thumb's share and place follow the history and the view's offset, and a place past either end of the track holds at it; under Xorg's dummy screen, after `seq 1 500`, the thumb stood at the bottom right, a drag to the top showed the first prompt and lines 1 to 39, and one back down lines 220 to 259; with mouse tracking on, the wheel went to the program as reports and did not scroll, while a drag on the thumb showed lines 245 to 284 | Pass |
| Click to move: a plain click on the line the shell reads, past its prompt, moves its cursor there with arrows | T59 | `term::click` tests: a click on the typed line counts the characters to it, left or right, past the end means the end, a wide character takes one arrow, soft-wrapped rows are one line, trailing spaces count up to the cursor, and a click on the prompt, another line, a line no prompt shows, output without marks or the alternate screen moves nothing; on macOS `shot` ran zsh with a marked prompt: after `echo hello` a click on the `h` logged `moves the cursor -5`, and `X` typed then ran `echo Xhello`; a drag sent nothing, and clicks on the prompt and on output logged `off the input` | Pass |
| Click to move: a plain click on the line the shell reads, past its prompt, moves its cursor there, the press itself to a shell that takes clicks, else arrows | T59 | `term::marks` test: A's `click_events=1` and `2` read as a press at the screen's row or counted from the prompt; `term::click` tests: a shell that takes clicks hears an SGR left press at the clicked cell, a row below the prompt too, none before the prompt, in the history or on the alternate screen; B reads the line its prompt started; a click on the typed line counts the characters to it, left or right, past the end means the end, a wide character takes one arrow, soft-wrapped rows are one line, trailing spaces count up to the cursor, and a click on the prompt, another line, a line no prompt shows, output without marks or the alternate screen moves nothing; on macOS `shot` ran zsh with a marked prompt: after `echo hello` a click on the `h` logged `moves the cursor -5`, and `X` typed then ran `echo Xhello`; a drag sent nothing, and clicks on the prompt and on output logged `off the input`; round 1 of T59's review found fish 4.9 a code point per Left, so a count put `X` inside `é`, and its redraw dropped the wrap flag a count followed: with fish's own `click_events=1` the press goes to fish, which then typed `echo Xéabc`, and a second click on the redrawn wrapped line put `Y` at row 2, column 3; zsh still counted 4 arrows; a press let go in another cell and one whose program turned on tracking before the release sent nothing | Pass |
| A bell makes no sound; the row marks it | T10, T12 | T12 review | Pass |
## 6. Main window and layout