T40: a press whose record went, a twin Linux stacked or one past the 64 kept, still shows its session; on Linux the body goes as markup's text; only a command C started posts its end, so a prompt bash 3.2 draws again posts nothing; the posting conditions are pure and tested; the decision says what macOS, Wayland and gpui's log do at a press and outside the bundle
CI / platforms (ubuntu-24.04) (push) Successful in 1m29s
CI / platforms (windows-latest) (push) Successful in 1m34s
CI / macos (push) Successful in 14m7s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-17 15:51:46 -04:00
1 parent efda50f142
commit 52576bb8ee
5 files changed
+98 -11

No files matched your search

+52 -9
View File
@@ -143,11 +143,7 @@ impl TerminalView {
/// Posts the state its row shows when it turned to waiting, done or error.
fn tell_state(&mut self, cx: &mut Context<Self>) {
let body = match self.records.shown() {
Some((id, record))
if matches!(record.state, State::Blocked | State::Done | State::Error) =>
{
line(&self.records, id, record)
}
Some((id, record)) if tells(record.state) => line(&self.records, id, record),
_ => return,
};
self.post(body, None, cx);
@@ -160,13 +156,21 @@ impl TerminalView {
log::info!("terminal {}: its last command ended {ended}", self.pty.pid);
}
self.ended = status;
self.post(finished(status), None, cx);
// Only a command C started posts its end, so a prompt bash 3.2 draws again posts nothing.
if std::mem::take(&mut self.ran) {
self.post(finished(status), None, cx);
} else {
log::debug!(
"terminal {}: an end without its start posts nothing",
self.pty.pid
);
}
}
/// Posts `body` while the window is not active or the session hidden, one every 3 seconds at most.
fn post(&mut self, body: String, asked: Option<&Notice>, cx: &mut Context<Self>) {
let pid = self.pty.pid;
if self.front && self.active {
if !unseen(self.front, self.active) {
log::debug!("terminal {pid}: in view, so it posts no {body:?}");
return;
}
@@ -181,6 +185,12 @@ impl TerminalView {
&self.title
});
log::info!("terminal {pid}: posts {}: {title:?}, {body:?}", posted.tag);
// Linux's notification daemons read the body as markup.
let body = if cfg!(target_os = "linux") {
escape(&body)
} else {
body
};
cx.show_system_notification(SystemNotification {
tag: posted.tag.clone(),
title: title.into(),
@@ -246,8 +256,18 @@ impl TerminalView {
/// A press on notification `tag`, if this session posted it: a program that asked hears of it; whether the session shows.
pub fn pressed(&mut self, tag: &str) -> Option<bool> {
let posted = self.notices.take(tag, None)?;
log::info!("terminal {}: notification {tag} pressed", self.pty.pid);
let pid = self.pty.pid;
let Some(posted) = self.notices.take(tag, None) else {
// A twin Linux stacked, pressed before, or one past the 64 kept, still shows its session.
let own = owns(pid, tag);
if own {
log::info!(
"terminal {pid}: notification {tag} pressed, its record gone, so it tells no program"
);
}
return own.then_some(true);
};
log::info!("terminal {pid}: notification {tag} pressed");
if posted.report {
let id = posted.id.as_deref().unwrap_or("0");
self.pty.write(notify::activated(id).into_bytes());
@@ -256,6 +276,29 @@ impl TerminalView {
}
}
/// Whether a session posts: not while it is the session shown in an active window.
fn unseen(front: bool, active: bool) -> bool {
!(front && active)
}
/// Whether the row's turn to `state` posts: waiting, done and error do.
fn tells(state: State) -> bool {
matches!(state, State::Blocked | State::Done | State::Error)
}
/// Whether program `pid`'s session posted notification `tag`: its own, `PID`, else `PID:ID` or `PID#N`.
fn owns(pid: u32, tag: &str) -> bool {
tag.strip_prefix(&pid.to_string())
.is_some_and(|rest| rest.is_empty() || rest.starts_with([':', '#']))
}
/// `text` as markup reads it as text.
fn escape(text: &str) -> String {
text.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
/// What a command's end says: it finished, or the status it failed with.
fn finished(status: Option<i32>) -> String {
match status {
+40
View File
@@ -81,3 +81,43 @@ fn a_notification_says_its_text_as_text() {
assert_eq!(finished(None), "Command finished");
assert_eq!(finished(Some(2)), "Command exited with 2");
}
#[test]
fn a_session_posts_unless_it_is_the_one_shown_in_an_active_window() {
assert!(!unseen(true, true));
assert!(unseen(true, false), "the window inactive");
assert!(unseen(false, true), "the session hidden");
assert!(unseen(false, false));
}
#[test]
fn waiting_done_and_error_post_and_work_and_rest_do_not() {
for (state, posts) in [
(State::Blocked, true),
(State::Done, true),
(State::Error, true),
(State::Working, false),
(State::Idle, false),
] {
assert_eq!(tells(state), posts, "{state:?}");
}
}
#[test]
fn a_tag_names_the_session_that_posted_it() {
for tag in ["12", "12:build", "12#3"] {
assert!(owns(12, tag), "{tag}");
}
for tag in ["123", "1", "123:build", "13#3", ""] {
assert!(!owns(12, tag), "{tag}");
}
}
#[test]
fn a_body_goes_as_markup_s_text() {
assert_eq!(
escape("<b>a</b> & <a href=x>"),
"&lt;b&gt;a&lt;/b&gt; &amp; &lt;a href=x&gt;"
);
assert_eq!(escape("plain"), "plain");
}
+4
View File
@@ -77,6 +77,8 @@ pub struct TerminalView {
pub(super) records: Records,
/// The status its shell's last command ended with; none before one, or when it gave none.
pub(super) ended: Option<i32>,
/// A command started, as OSC 133 C says, since the last one ended.
pub(super) ran: bool,
/// The system notifications it posted.
pub(super) notices: Notices,
/// The program's title; empty while it names none.
@@ -134,6 +136,7 @@ impl TerminalView {
reports: 0,
records: Records::default(),
ended: None,
ran: false,
notices: Notices::default(),
title: String::new(),
front: true,
@@ -263,6 +266,7 @@ impl TerminalView {
self.records.prompt();
self.restate(cx);
}
PtyEvent::Mark(Mark::Output) => self.ran = true,
PtyEvent::Mark(_) => {}
PtyEvent::Status(status) => {
self.status(status);