mod balls;
pub mod cadence;
mod deletes;
mod derive;
mod dirty;
mod drift;
mod echo;
mod focus;
mod grace;
mod knobs;
mod line;
mod live;
mod memo;
mod ops;
mod panels;
mod roots;
mod search;
mod snapshot;
mod spend;
mod view;
use crate::binding::Workspace;
use crate::fs_watcher::RootKind;
use crate::git_tree::{AgentState, GitTree};
use crate::keymap::InspectorTab;
use crate::projects::runner::BlRunner;
use crate::state::{
DirtySet, SearchCell, SnapshotCell, TailCell, latest_snapshot, new_snapshot_cell,
};
use crate::ui_state::{Clock, UiState};
use crate::watch::Mark;
use clap::Parser;
use std::path::PathBuf;
use std::sync::Arc;
pub use self::cadence::Cadence;
pub use self::derive::Deriver;
pub use self::derive::worker::Worker;
pub use self::grace::WoundGrace;
pub use self::live::{FollowThread, Follower, LiveTail};
pub(crate) use self::memo::SnapMemo;
pub use self::roots::Roots;
pub use self::snapshot::{Growth, Snapshot};
#[derive(Parser, Debug, Clone, PartialEq, Eq)]
#[command(version, about = "yog: egui frontend for lernie loops")]
pub struct Args {
#[arg(long)]
pub workspace: Option<PathBuf>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Focus {
pub ws: Option<PathBuf>,
pub agent: Option<String>,
pub tab: InspectorTab,
}
pub struct AppModel {
roots: Roots,
ui: UiState,
focus: Focus,
pub(crate) snap: Arc<Snapshot>,
derived: Arc<Snapshot>,
folded: Option<echo::Echo>,
cell: SnapshotCell,
dirty: DirtySet,
clock: Arc<dyn Clock>,
identity_user: Option<String>,
search: SearchCell,
started: Option<echo::Echo>,
tail: TailCell,
followed: Option<Arc<LiveTail>>,
}
impl AppModel {
pub fn boot(
roots: Roots,
initial_focus: Option<PathBuf>,
clock: Arc<dyn Clock>,
balls: Box<dyn BlRunner>,
user: Option<String>,
) -> (Self, Deriver) {
let ui = UiState::open(roots.ui_json());
let dirty = DirtySet::default();
let cell = new_snapshot_cell(Arc::new(Snapshot::empty(clock.now())));
let mut deriver = Deriver::new(
roots.clone(),
Arc::clone(&clock),
balls,
dirty.clone(),
Arc::clone(&cell),
);
deriver.boot();
let derived = latest_snapshot(&cell);
let mut model = Self {
snap: Arc::clone(&derived),
derived,
folded: None,
roots,
ui,
focus: Focus::default(),
cell,
dirty,
clock,
identity_user: user,
search: SearchCell::default(),
started: None,
tail: TailCell::default(),
followed: None,
};
model.focus = model.startup_focus(initial_focus, &Arc::clone(&model.snap).workspaces);
(model, deriver)
}
pub fn refresh(&mut self) -> bool {
let latest = latest_snapshot(&self.cell);
let landed = !Arc::ptr_eq(&self.derived, &latest);
if landed {
self.derived = latest;
self.adopt_ui();
}
self.adopt_started();
crate::state::follow(&self.tail, self.followed_subject());
let followed = crate::state::taken_tail(&self.tail);
if landed || self.started != self.folded || followed != self.followed {
self.folded = self.started.clone();
self.followed = followed;
self.snap = echo::compose(
&self.derived,
self.started.as_ref(),
self.followed.as_deref(),
);
}
self.ack_focused();
landed
}
fn adopt_ui(&mut self) {
if let Some(bytes) = self.derived.ui_bytes.clone()
&& !self.ui.is_echo(&bytes)
{
self.ui.adopt(&bytes);
}
}
pub(crate) fn mark_dirty<I: IntoIterator<Item = PathBuf>>(&self, roots: I) {
self.dirty
.mark_all(roots.into_iter().map(|r| (r, Mark::Watch)));
}
}
fn desired_watches(roots: &Roots, workspaces: &[Workspace]) -> Vec<(PathBuf, RootKind)> {
let mut desired = vec![
(roots.names(), RootKind::NamesRoot),
(roots.workspaces(), RootKind::WorkspacesRoot),
(roots.replays(), RootKind::WorkspacesRoot),
(roots.yog_state.clone(), RootKind::YogState),
(roots.balls_clones.clone(), RootKind::BallsClones),
];
desired.extend(
workspaces
.iter()
.map(|w| (w.path.clone(), RootKind::Workspace)),
);
desired
}
fn needs_liveness_reprobe(tree: &GitTree) -> bool {
tree.agents
.iter()
.any(|a| matches!(a.state, AgentState::Live | AgentState::InFlight))
}
#[cfg(test)]
mod tests;