T39: review round 1: the view notes the state its row shows after each change it hears and tells the workspace of each, so a hidden session that waits, works and waits again in one output starts waiting again; hover reads the records and the last command's status, which the view now keeps, at each frame, stays under the pointer and scrolls within the window; the progress ring holds Ely's loader, since its track can match a selected row's ground
CI / platforms (windows-latest) (push) Successful in 1m34s
CI / platforms (ubuntu-24.04) (push) Successful in 2m8s
CI / macos (push) Successful in 16m38s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-17 15:12:29 -04:00
1 parent 85bcbf466f
commit 53b8978dff
10 files changed
+119 -61

No files matched your search

+2 -2
View File
@@ -78,7 +78,7 @@ pub fn follow(cx: &mut Context<TerminalView>) -> Task<()> {
states.clear();
let gone = view.update(cx, |view, cx| {
if view.records.herdr(&[]) {
cx.emit(TerminalEvent::Records);
view.restate(cx);
}
});
if gone.is_err() {
@@ -128,7 +128,7 @@ pub fn follow(cx: &mut Context<TerminalView>) -> Task<()> {
let still = view.reports == reports;
if still {
if view.records.herdr(&agents) {
cx.emit(TerminalEvent::Records);
view.restate(cx);
}
if let Some(folder) = report.clone() {
cx.emit(TerminalEvent::Folder(folder));
+2 -2
View File
@@ -9,7 +9,7 @@ use gpui::{
use super::{
keys::{self, Action, Key, Kitty},
view::{TerminalEvent, TerminalView},
view::TerminalView,
};
actions!(elyterminal, [Find, FindNewer, PreviousPrompt, NextPrompt]);
@@ -78,7 +78,7 @@ impl TerminalView {
drop(term);
self.pty.write(bytes);
if self.records.seen() {
cx.emit(TerminalEvent::Records);
self.restate(cx);
}
cx.notify();
}
+15
View File
@@ -198,6 +198,8 @@ pub struct Records {
clock: u64,
/// A program has reported through OSC 7501 since the last full reset, so OSC 9;4 maps no more.
reported: bool,
/// The state a row showed at the last note.
shows: Option<State>,
}
impl Records {
@@ -317,6 +319,19 @@ impl Records {
})
}
/// Notes the state a row shows now; true when it changed since the last note.
pub fn restate(&mut self) -> bool {
let now = self.shown().map(|record| record.state);
let changed = now != self.shows;
self.shows = now;
changed
}
/// The state a row showed at the last note.
pub fn shows(&self) -> Option<State> {
self.shows
}
/// Each record with its id, in the order first reported.
pub fn iter(&self) -> impl Iterator<Item = (&str, &Record)> {
self.list
+18
View File
@@ -371,3 +371,21 @@ fn typing_sees_done_and_error_but_herdr_s() {
assert_eq!(ids, ["c", "herdr:w1:p1"]);
assert!(!records.seen(), "nothing more to see");
}
#[test]
fn each_change_of_the_state_a_row_shows_is_noted() {
let mut records = Records::default();
assert!(!records.restate(), "none shows yet");
records.report(String::new(), record(State::Blocked, None));
assert!(records.restate());
records.report(String::new(), record(State::Working, None));
assert!(records.restate());
records.report(String::new(), record(State::Blocked, None));
assert!(records.restate(), "waiting again after work in one output");
assert_eq!(records.shows(), Some(State::Blocked));
records.report("b".into(), record(State::Working, None));
assert!(!records.restate(), "the row shows what it showed");
records.reset();
assert!(records.restate());
assert_eq!(records.shows(), None);
}
+47 -20
View File
@@ -28,7 +28,7 @@ use super::{
paint::{self, Look},
pointer::Pointer,
pty::{Events, Pty, PtyEvent, config, fit},
status::{self, Records, Status},
status::{self, Records, State, Status},
zoom,
};
use crate::{
@@ -55,8 +55,8 @@ pub enum TerminalEvent {
Finished(Option<i32>),
/// A notification to show or close, OSC 9, 777 or 99.
Notice(Ask),
/// Its records may have changed.
Records,
/// The state its row shows changed, to this one.
Shows(Option<State>),
Exited(ExitStatus),
}
@@ -79,6 +79,8 @@ pub struct TerminalView {
pub(super) reports: u64,
/// What programs say they are doing, OSC 7501.
pub(super) records: Records,
/// The status its shell's last command ended with; none before one, or when it gave none.
ended: Option<i32>,
cell: (u16, u16),
/// Focused in an active window, as the program last heard.
focused: bool,
@@ -127,6 +129,7 @@ impl TerminalView {
size,
reports: 0,
records: Records::default(),
ended: None,
cell: (8, 16),
focused: false,
blinking: false,
@@ -155,6 +158,30 @@ impl TerminalView {
&self.records
}
/// The status its shell's last command ended with.
pub fn ended(&self) -> Option<i32> {
self.ended
}
/// Notes the state its row shows, and tells the owner of each change.
pub(super) fn restate(&mut self, cx: &mut Context<Self>) {
if self.records.restate() {
let state = self.records.shows();
log::info!("terminal {}: its row shows {state:?}", self.pty.pid);
cx.emit(TerminalEvent::Shows(state));
}
}
/// A command the shell ran ended with `status`.
fn finished(&mut self, status: Option<i32>, cx: &mut Context<Self>) {
if self.ended != status {
let ended = status.map_or("with no status".into(), |code| format!("with {code}"));
log::info!("terminal {}: its last command ended {ended}", self.pty.pid);
}
self.ended = status;
cx.emit(TerminalEvent::Finished(status));
}
fn pump(mut events: Events, cx: &mut Context<Self>) -> Task<()> {
cx.spawn(async move |view, cx| {
while let Some(first) = events.next().await {
@@ -227,15 +254,6 @@ impl TerminalView {
let drawn = events
.iter()
.any(|event| matches!(event, PtyEvent::Term(Event::Wakeup)));
let restated = events.iter().any(|event| {
matches!(
event,
PtyEvent::Mark(Mark::Prompt)
| PtyEvent::Status(Status::Report(..) | Status::Clear(_))
| PtyEvent::Ask(Ask::Progress(_))
| PtyEvent::Reset
)
});
for event in events {
match event {
PtyEvent::Folder(folder) => {
@@ -244,22 +262,31 @@ impl TerminalView {
}
PtyEvent::Term(event) => self.term_event(event, cx),
PtyEvent::Mouse(mode) => self.mouse = mode,
PtyEvent::Mark(Mark::Done(status)) => cx.emit(TerminalEvent::Finished(status)),
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.records.prompt(),
PtyEvent::Mark(Mark::Prompt) => {
self.records.prompt();
self.restate(cx);
}
PtyEvent::Mark(_) => {}
PtyEvent::Status(status) => self.status(status),
PtyEvent::Ask(ask) => self.ask(ask, cx),
PtyEvent::Reset => self.records.reset(),
PtyEvent::Status(status) => {
self.status(status);
self.restate(cx);
}
PtyEvent::Ask(ask) => {
self.ask(ask, cx);
self.restate(cx);
}
PtyEvent::Reset => {
self.records.reset();
self.restate(cx);
}
}
}
// ponytail: a full search per output batch while find is open; search only new lines if it lags.
if drawn && self.finder.is_some() {
self.search(false, cx);
}
if restated {
cx.emit(TerminalEvent::Records);
}
cx.notify();
}