#![forbid(unsafe_code)]
pub mod attach;
pub mod binding;
pub mod definition;
pub mod definition_index;
pub mod frecency;
pub mod index;
pub mod instance_registry;
pub mod instantiate;
pub mod picker;
pub mod project;
pub mod record;
pub use attach::{decide, decide_with_root, AttachAction, AttachDecision, AttachPolicy};
pub use binding::ProjectBinding;
pub use definition::{DefinitionError, SessionDefinition, SessionOrigin};
pub use definition_index::DefinitionIndex;
pub use index::{best_match, rank, rank_mixed, rank_union, Ranked, Searchable, SessionIndex};
pub use picker::{union_view, UnionRow};
pub use instance_registry::InstanceRegistry;
pub use instantiate::{instantiate, reinstantiate, InstantiateError};
pub use project::{find_project_root, find_project_root_with, project_root, project_root_with};
pub use record::{display_name_for, identity_for, NameStyle, SessionRecord, SessionState, ThemeMirror};
pub use snapshot::{PolicyMirror, PracaSnapshot};
pub use ishou_tokens::{SessionNameStyle, SessionTheme};
pub mod snapshot;
use std::path::Path;
use tear_types::id::SessionId;
#[derive(Clone, Debug)]
pub struct Praca {
pub index: SessionIndex,
pub binding: ProjectBinding,
pub definitions: DefinitionIndex,
pub policy: AttachPolicy,
pub name_style: SessionNameStyle,
}
impl Default for Praca {
fn default() -> Self {
Self::new()
}
}
impl Praca {
#[must_use]
pub fn new() -> Self {
Self {
index: SessionIndex::new(),
binding: ProjectBinding::new(),
definitions: DefinitionIndex::new(),
policy: AttachPolicy::AutoSwitch,
name_style: SessionNameStyle::default(),
}
}
#[must_use]
pub fn with(
index: SessionIndex,
binding: ProjectBinding,
policy: AttachPolicy,
name_style: SessionNameStyle,
) -> Self {
Self {
index,
binding,
definitions: DefinitionIndex::new(),
policy,
name_style,
}
}
#[must_use]
pub fn on_cwd_change(
&self,
current_id: Option<SessionId>,
new_cwd: &Path,
now: u64,
) -> AttachDecision {
let _ = now;
let current = current_id.and_then(|id| self.index.get(id));
attach::decide(
self.policy,
current,
new_cwd,
&self.binding,
&self.index,
self.name_style,
)
}
pub fn record_visit(&mut self, id: SessionId, now: u64) -> bool {
if let Some(r) = self.index.get_mut(id) {
r.visits = r.visits.saturating_add(1);
r.last_seen = now;
true
} else {
false
}
}
#[must_use]
pub fn search(&self, query: &str, now: u64) -> Vec<&SessionRecord> {
self.index.search(query, now)
}
#[must_use]
pub fn to_snapshot(&self) -> PracaSnapshot {
PracaSnapshot {
index: self.index.clone(),
binding: self.binding.clone(),
definitions: self.definitions.clone(),
policy: self.policy.into(),
name_style: self.name_style.into(),
}
}
#[must_use]
pub fn from_snapshot(snap: PracaSnapshot) -> Self {
Self {
index: snap.index,
binding: snap.binding,
definitions: snap.definitions,
policy: snap.policy.into(),
name_style: snap.name_style.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn sid(s: &str) -> SessionId {
SessionId::from_seed(s)
}
#[test]
fn facade_defaults_are_automation_first() {
let p = Praca::new();
assert_eq!(p.policy, AttachPolicy::AutoSwitch);
assert_eq!(p.name_style, SessionNameStyle::Emoji);
assert!(p.index.is_empty());
}
#[test]
fn on_cwd_change_spawns_for_new_project() {
let p = Praca::new();
let d = p.on_cwd_change(None, Path::new("/code/pleme-io/brand-new"), 100);
assert!(matches!(d, AttachDecision::SpawnNew { .. }));
}
#[test]
fn on_cwd_change_switches_to_bound_session() {
let mut p = Praca::new();
let rec =
SessionRecord::for_project(sid("tear-sess"), PathBuf::from("/code/tear"), SessionNameStyle::Emoji, 0);
p.index.upsert(rec);
p.binding.bind(PathBuf::from("/code/tear"), sid("tear-sess"));
let d = p.on_cwd_change(None, Path::new("/code/tear"), 100);
assert_eq!(d, AttachDecision::SwitchTo(sid("tear-sess")));
}
#[test]
fn record_visit_bumps_and_stamps() {
let mut p = Praca::new();
let rec = SessionRecord::for_project(sid("s"), PathBuf::from("/x"), SessionNameStyle::Emoji, 10);
p.index.upsert(rec);
let before = p.index.get(sid("s")).unwrap().visits;
assert!(p.record_visit(sid("s"), 999));
let after = p.index.get(sid("s")).unwrap();
assert_eq!(after.visits, before + 1);
assert_eq!(after.last_seen, 999);
assert!(!p.record_visit(sid("nope"), 999));
}
#[test]
fn search_delegates_to_index() {
let mut p = Praca::new();
p.index.upsert(SessionRecord::for_project(
sid("a"),
PathBuf::from("/code/mado"),
SessionNameStyle::Emoji,
100,
));
let out = p.search("mado", 100);
assert_eq!(out.len(), 1);
assert_eq!(out[0].id, sid("a"));
}
}