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
This commit is contained in:
5 files changed
+98
-11
No files matched your search
+52
-9
@@ -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('&', "&")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
}
|
||||
|
||||
/// What a command's end says: it finished, or the status it failed with.
|
||||
fn finished(status: Option<i32>) -> String {
|
||||
match status {
|
||||
|
||||
@@ -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>"),
|
||||
"<b>a</b> & <a href=x>"
|
||||
);
|
||||
assert_eq!(escape("plain"), "plain");
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user