aboutsummaryrefslogtreecommitdiff
path: root/crates/dinggerd/src/tmux.rs
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-10-20 11:53:23 +0300
committerIgor Soarez <igor@soarez.org>2026-10-20 11:53:23 +0300
commit3509e48c5776c4153d8f90aff83a796c6d383208 (patch)
treeb19a75889a12f7c8ae828f61b8d70130e6ebe968 /crates/dinggerd/src/tmux.rs
parentfed066bc3908d18ede5490130975203a56c48fc2 (diff)
adopt herdr's five-status model with symbols style and focus detection
statuses are now herdr's verbatim: working (yellow), blocked (red), done (teal, finished and unattended), idle (green, attended since its last transition), unknown (gray) - priority blocked > done > working > idle > unknown. the daemon watches tmux focus (pane_active of attached clients) to mark panes attended, which is what separates done from idle. menu bar renders symbols-style glyphs with herdr's colors in priority order; the tmux segment stays after the clock and renders done/blocked/ working counts from numeric options. deployment note: do not strip the binary - it invalidates the ad-hoc signature and macOS kills it on launch.
Diffstat (limited to 'crates/dinggerd/src/tmux.rs')
-rw-r--r--crates/dinggerd/src/tmux.rs67
1 files changed, 38 insertions, 29 deletions
diff --git a/crates/dinggerd/src/tmux.rs b/crates/dinggerd/src/tmux.rs
index cba1ca6..f6c2f17 100644
--- a/crates/dinggerd/src/tmux.rs
+++ b/crates/dinggerd/src/tmux.rs
@@ -2,15 +2,14 @@
use std::process::Command;
-use detect_core::manifest::agent_state_label;
use detect_core::{identify_agent, Agent, AgentState};
#[derive(Debug, Clone)]
pub struct Pane {
/// Unique tmux pane id, e.g. `%3`. Stable for the pane's lifetime.
pub pane_id: String,
- /// User-facing target, e.g. `main:1.0`.
- pub target: String,
+ /// Human-readable target, e.g. `main:work` (`.pane` suffix when split).
+ pub display_target: String,
/// Foreground command name in the pane, from `#{pane_current_command}`.
pub current_command: String,
/// Last OSC title the pane's application set, from `#{pane_title}`.
@@ -29,14 +28,21 @@ pub struct Pane {
/// The `@dingger_ack` user option: "1" when the user already jumped to
/// this pane in its current ready episode.
pub written_ack: String,
+ /// The pane is the active pane of the active window.
+ pub pane_active: bool,
}
impl Pane {
- /// Panes the user is actively looking at suppress notifications, mirroring
- /// herdr's active-tab suppression.
+ /// Panes the user is actively looking at suppress finished notifications,
+ /// mirroring herdr's active-tab suppression.
pub fn suppresses_notifications(&self) -> bool {
self.window_active && self.session_attached
}
+
+ /// The pane is fully focused: the user is looking at this exact pane.
+ pub fn attended(&self) -> bool {
+ self.pane_active && self.suppresses_notifications()
+ }
}
fn run_tmux(args: &[&str]) -> Result<String, String> {
@@ -64,7 +70,6 @@ fn run_tmux(args: &[&str]) -> Result<String, String> {
pub fn list_panes() -> Result<Vec<Pane>, String> {
let format = concat!(
"#{pane_id}\t",
- "#{session_name}:#{window_index}.#{pane_index}\t",
"#{pane_current_command}\t",
"#{pane_title}\t",
"#{pane_dead}\t",
@@ -74,18 +79,22 @@ pub fn list_panes() -> Result<Vec<Pane>, String> {
"#{pane_pid}\t",
"#{@agent_state}\t",
"#{@agent}\t",
- "#{@dingger_ack}"
+ "#{@dingger_ack}\t",
+ "#{pane_active}\t",
+ "#{session_name}:#{window_name}\t",
+ "#{window_panes}\t",
+ "#{pane_index}",
);
let out = run_tmux(&["list-panes", "-a", "-F", format])?;
let mut panes = Vec::new();
for line in out.lines() {
let fields: Vec<&str> = line.split('\t').collect();
- if fields.len() < 11 {
+ if fields.len() < 15 {
continue;
}
- let current_command = fields[2].to_string();
- let start_command = fields[7].to_string();
- let pane_pid: Option<u32> = fields[8].trim().parse().ok();
+ let current_command = fields[1].to_string();
+ let start_command = fields[6].to_string();
+ let pane_pid: Option<u32> = fields[7].trim().parse().ok();
// An agent may be started via a wrapper or still be initializing
// behind the shell; `pane_start_command` covers launch-command cases.
let mut agent =
@@ -97,18 +106,25 @@ pub fn list_panes() -> Result<Vec<Pane>, String> {
let tree = process_tree.get_or_insert_with(fetch_process_tree);
agent = resolve_agent_in_process_tree(pane_pid, tree);
}
+ // Display target: session:window-name, pane index only when split.
+ let mut display_target = fields[12].to_string();
+ if fields[13].parse::<u32>().map(|n| n > 1).unwrap_or(false) {
+ display_target.push('.');
+ display_target.push_str(fields[14]);
+ }
panes.push(Pane {
pane_id: fields[0].to_string(),
- target: fields[1].to_string(),
+ display_target,
current_command,
- pane_title: fields[3].to_string(),
- dead: fields[4] == "1",
- window_active: fields[5] == "1",
- session_attached: fields[6] != "0",
+ pane_title: fields[2].to_string(),
+ dead: fields[3] == "1",
+ window_active: fields[4] == "1",
+ session_attached: fields[5] != "0",
agent,
- written_state: fields[9].to_string(),
- written_agent: fields[10].to_string(),
- written_ack: fields.get(11).copied().unwrap_or("").to_string(),
+ written_state: fields[8].to_string(),
+ written_agent: fields[9].to_string(),
+ written_ack: fields[10].to_string(),
+ pane_active: fields[11] == "1",
});
}
Ok(panes)
@@ -190,16 +206,9 @@ pub fn capture_pane(pane_id: &str) -> Result<String, String> {
run_tmux(&["capture-pane", "-p", "-q", "-t", pane_id])
}
-pub fn set_agent_state(pane_id: &str, state: AgentState) -> Result<(), String> {
- run_tmux(&[
- "set-option",
- "-p",
- "-t",
- pane_id,
- "@agent_state",
- agent_state_label(state),
- ])
- .map(|_| ())
+/// Write the pane's display status (herdr's vocabulary: done/idle/...).
+pub fn set_agent_state(pane_id: &str, label: &str) -> Result<(), String> {
+ run_tmux(&["set-option", "-p", "-t", pane_id, "@agent_state", label]).map(|_| ())
}
/// Label the pane with the agent running in it, for tmux status-line and