aboutsummaryrefslogtreecommitdiff
path: root/crates/dinggerd
diff options
context:
space:
mode:
Diffstat (limited to 'crates/dinggerd')
-rw-r--r--crates/dinggerd/src/main.rs3
-rw-r--r--crates/dinggerd/src/watcher.rs83
2 files changed, 56 insertions, 30 deletions
diff --git a/crates/dinggerd/src/main.rs b/crates/dinggerd/src/main.rs
index 9606d5b..2783899 100644
--- a/crates/dinggerd/src/main.rs
+++ b/crates/dinggerd/src/main.rs
@@ -253,8 +253,7 @@ fn status() -> Result<(), String> {
}
panes.sort_by_key(|pane| {
let status = watcher::Status::from_label(&pane.written_state);
- let since = pane.written_since.parse::<u64>().unwrap_or(u64::MAX);
- (std::cmp::Reverse(status.priority()), since, pane.display_target.clone())
+ (watcher::list_order(status, pane.written_since.parse().ok()), pane.display_target.clone())
});
let place = |pane: &tmux::Pane| pane.display_target.replacen(':', " ยท ", 1);
let width = panes.iter().map(|pane| place(pane).chars().count()).max().unwrap_or(5).max(5);
diff --git a/crates/dinggerd/src/watcher.rs b/crates/dinggerd/src/watcher.rs
index 346bfa8..8951623 100644
--- a/crates/dinggerd/src/watcher.rs
+++ b/crates/dinggerd/src/watcher.rs
@@ -20,7 +20,7 @@
//! up exactly where it left off:
//!
//! - `@agent_state` display status (what every surface renders)
-//! - `@dingger_since` unix ms the pane started needing the user (FIFO)
+//! - `@dingger_since` unix ms the pane entered its current status
//! - `@dingger_hook` last state an agent extension reported
//! - `@dingger_ack` one-shot "the user jumped here" event from `dingger focus`
//! - `@agent_session` the agent's session reference, for restore
@@ -100,8 +100,9 @@ pub fn notification_kind_for_transition(
pub struct PaneTrack {
pub state: AgentState,
pub seen: bool,
- /// Unix millisecond the pane started needing the user (done or blocked);
- /// orders the FIFO queue within a status.
+ /// Unix millisecond the pane entered its current status: the queue
+ /// serves done and blocked oldest first; working and idle are listed
+ /// most recent first.
pub since: Option<u64>,
}
@@ -133,12 +134,7 @@ impl PaneTrack {
"blocked" => (AgentState::Blocked, true),
_ => return PaneTrack::NEW,
};
- let track = PaneTrack { state, seen, since };
- let needs_user = matches!(track.status(), Status::Done | Status::Blocked);
- PaneTrack {
- since: if needs_user { since } else { None },
- ..track
- }
+ PaneTrack { state, seen, since }
}
}
@@ -165,20 +161,28 @@ pub fn advance(
} else if matches!(prev.state, AgentState::Working | AgentState::Blocked) {
next.seen = attended;
}
- next.since = None; // a new episode
}
if attended || acked {
next.seen = true;
}
- let needs_user = matches!(next.status(), Status::Done | Status::Blocked);
- next.since = match (needs_user, next.since) {
- (true, Some(since)) => Some(since),
- (true, None) => Some(now),
- (false, _) => None,
- };
+ if next.status() != prev.status() || next.since.is_none() {
+ next.since = Some(now);
+ }
next
}
+/// The order agents are listed in: herdr's status priority; within a
+/// status, done and blocked oldest first (the queue: nothing waits
+/// forever), working and idle most recent first; then by place.
+pub fn list_order(status: Status, since: Option<u64>) -> (std::cmp::Reverse<u8>, i128) {
+ let since = since.map(i128::from);
+ let key = match status {
+ Status::Done | Status::Blocked => since.unwrap_or(i128::MAX),
+ _ => since.map(|since| -since).unwrap_or(i128::MAX),
+ };
+ (std::cmp::Reverse(status.priority()), key)
+}
+
pub struct Watcher {
tracks: HashMap<String, PaneTrack>,
snapshots: crate::snapshot::Recorder,
@@ -363,7 +367,9 @@ impl Watcher {
Status::Working => working += 1,
Status::Idle | Status::Unknown => {}
}
- if let Some(since) = track.since {
+ // The queue: what needs the user, oldest first.
+ if matches!(track.status(), Status::Blocked | Status::Done) {
+ let since = track.since.unwrap_or(u64::MAX);
queue.push((std::cmp::Reverse(track.status().priority()), since, pane.pane_id.as_str()));
}
}
@@ -498,7 +504,7 @@ mod tests {
fn first_observation_of_idle_is_idle() {
let track = step(PaneTrack::NEW, Idle, false);
assert_eq!(track.status(), Status::Idle);
- assert_eq!(track.since, None);
+ assert_eq!(track.since, Some(100));
}
// herdr: state_changed_idle_in_background_marks_unseen
@@ -515,9 +521,9 @@ mod tests {
#[test]
fn completion_while_looking_is_idle() {
let working = step(PaneTrack::NEW, Working, true);
- let idle = step(working, Idle, true);
+ let idle = advance(working, Idle, true, false, 150);
assert_eq!(idle.status(), Status::Idle);
- assert_eq!(idle.since, None);
+ assert_eq!(idle.since, Some(150), "idle since the turn ended");
}
// herdr: switch_workspace_marks_panes_seen (mark_active_tab_seen)
@@ -527,9 +533,9 @@ mod tests {
assert_eq!(done.status(), Status::Done);
let still_done = step(done, Idle, false);
assert_eq!(still_done.status(), Status::Done);
- let seen = step(still_done, Idle, true);
+ let seen = advance(still_done, Idle, true, false, 170);
assert_eq!(seen.status(), Status::Idle);
- assert_eq!(seen.since, None);
+ assert_eq!(seen.since, Some(170), "idle since the user looked");
}
// herdr: agent_focus_marks_already_focused_done_agent_seen
@@ -543,9 +549,9 @@ mod tests {
#[test]
fn working_again_clears_done_and_the_next_completion_is_done_again() {
let done = step(step(PaneTrack::NEW, Working, false), Idle, false);
- let working = step(done, Working, false);
+ let working = advance(done, Working, false, false, 250);
assert_eq!(working.status(), Status::Working);
- assert_eq!(working.since, None);
+ assert_eq!(working.since, Some(250));
let done_again = advance(working, Idle, false, false, 300);
assert_eq!(done_again.status(), Status::Done);
assert_eq!(done_again.since, Some(300));
@@ -562,10 +568,31 @@ mod tests {
}
#[test]
- fn fifo_stamp_survives_unchanged_ticks() {
+ fn the_stamp_survives_unchanged_ticks() {
let done = advance(step(PaneTrack::NEW, Working, false), Idle, false, false, 50);
- let later = advance(done, Idle, false, false, 999);
- assert_eq!(later.since, Some(50));
+ assert_eq!(advance(done, Idle, false, false, 999).since, Some(50));
+ let idle = advance(done, Idle, true, false, 60);
+ assert_eq!(advance(idle, Idle, true, false, 999).since, Some(60));
+ }
+
+ #[test]
+ fn waiting_oldest_first_the_rest_most_recent_first() {
+ let mut rows = [
+ ("idle old", Status::Idle, Some(10)),
+ ("done new", Status::Done, Some(90)),
+ ("idle unknown", Status::Idle, None),
+ ("working old", Status::Working, Some(20)),
+ ("idle new", Status::Idle, Some(80)),
+ ("done old", Status::Done, Some(30)),
+ ("blocked", Status::Blocked, Some(99)),
+ ("working new", Status::Working, Some(70)),
+ ];
+ rows.sort_by_key(|(_, status, since)| list_order(*status, *since));
+ let names: Vec<_> = rows.iter().map(|(name, ..)| *name).collect();
+ assert_eq!(
+ names,
+ ["blocked", "done old", "done new", "working new", "working old", "idle new", "idle old", "idle unknown"]
+ );
}
#[test]
@@ -583,7 +610,7 @@ mod tests {
assert_eq!(step(done, Idle, false).status(), Status::Done);
assert_eq!(PaneTrack::restore("idle", "").status(), Status::Idle);
assert_eq!(PaneTrack::restore("", "").status(), Status::Unknown);
- assert_eq!(PaneTrack::restore("idle", "42").since, None);
+ assert_eq!(PaneTrack::restore("idle", "42").since, Some(42), "recency survives a restart");
}
#[test]