use super::resource::{TabKeys, ref_str};
use super::session::OrcaBackend;
use crate::backend::*;
use chrono::{SecondsFormat, Utc};
use serde::Serialize;
use std::io::Write;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorktreePolicy {
Host,
Inherit,
Selector(String),
}
impl WorktreePolicy {
pub fn from_config(value: &str) -> Self {
match value.trim() {
"" | "auto" | "host" => Self::Host,
"inherit" => Self::Inherit,
selector => Self::Selector(selector.to_string()),
}
}
}
fn selector_path(selector: &str) -> Option<PathBuf> {
let path = selector
.strip_prefix("path:")
.or_else(|| selector.split_once("::").map(|(_, path)| path))?;
let path = PathBuf::from(path);
path.is_absolute().then_some(path)
}
pub(super) fn absolute(path: &Path) -> PathBuf {
if let Ok(canonical) = std::fs::canonicalize(path) {
return canonical;
}
if path.is_absolute() {
return path.to_path_buf();
}
std::env::current_dir()
.map(|cwd| cwd.join(path))
.unwrap_or_else(|_| path.to_path_buf())
}
#[derive(Debug, Clone)]
pub(super) struct TabMemo {
pub(super) root: PathBuf,
pub(super) role: String,
pub(super) session_id: String,
pub(super) title: String,
}
#[derive(Debug, Clone, Serialize)]
struct TabLine<'a> {
pane_key: &'a str,
handle: &'a str,
task_id: &'a str,
session_id: &'a str,
role: &'a str,
worktree_selector: &'a str,
title: &'a str,
state: &'a str,
updated_at: String,
}
fn append_tab_line(root: &Path, line: &TabLine<'_>) -> std::io::Result<()> {
let dir = root.join(".onlyne/cache");
std::fs::create_dir_all(&dir)?;
let mut text =
serde_json::to_string(line).map_err(|error| std::io::Error::other(error.to_string()))?;
text.push('\n');
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(dir.join("orca-tabs.jsonl"))?
.write_all(text.as_bytes())
}
pub(super) fn host_worktree_env() -> Option<String> {
std::env::var("ORCA_WORKTREE_ID")
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
}
impl OrcaBackend {
pub(super) fn selector_for(&self) -> Option<String> {
match &self.policy {
WorktreePolicy::Host => self.host_worktree.clone(),
WorktreePolicy::Inherit => None,
WorktreePolicy::Selector(selector) => Some(selector.clone()),
}
}
pub(super) fn note(&self, session: &SessionRef, state: &str) {
let keys = TabKeys::from_ref(session);
let (Some(pane_key), Some(handle)) = (keys.pane_key.as_deref(), keys.handle.as_deref())
else {
tracing::warn!(
task = %session.task_id,
"orca terminal has no pane key or handle; the tab map stays unwritten"
);
return;
};
let memo = self.tabs.lock().get(pane_key).cloned();
let selector = ref_str(session, "selector");
let root = memo
.as_ref()
.map(|memo| memo.root.clone())
.or_else(|| selector.as_deref().and_then(selector_path));
let Some(root) = root else {
tracing::warn!(
task = %session.task_id,
pane_key,
"no workspace root for the orca tab map; the line is skipped"
);
return;
};
let line = TabLine {
pane_key,
handle,
task_id: &session.task_id,
session_id: memo
.as_ref()
.map(|memo| memo.session_id.as_str())
.unwrap_or_default(),
role: memo
.as_ref()
.map(|memo| memo.role.as_str())
.unwrap_or_default(),
worktree_selector: selector.as_deref().unwrap_or_default(),
title: memo
.as_ref()
.map(|memo| memo.title.as_str())
.unwrap_or_default(),
state,
updated_at: Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true),
};
if let Err(error) = append_tab_line(&root, &line) {
tracing::warn!(
error = %error,
task = %session.task_id,
"orca tab map line was not written"
);
}
}
}