use super::*;
#[derive(Debug, Default)]
pub(crate) struct LineSpecs {
pub(crate) explicit: BTreeSet<usize>,
pub(crate) ranges: Vec<(usize, usize)>,
}
impl LineSpecs {
pub(crate) fn all(&self) -> BTreeSet<usize> {
let mut out = self.explicit.clone();
for &(a, b) in &self.ranges {
out.extend(a..=b);
}
out
}
}
pub(crate) fn parse_line_specs(tokens: &[String]) -> Result<Vec<(bool, crate::text::RangeSpec)>> {
let mut out = Vec::new();
for tok in tokens {
let t = tok.trim();
if t.is_empty() {
continue;
}
let is_explicit = !t.contains("..");
out.push((
is_explicit,
crate::text::parse_range_spec(t, "--line", true)?,
));
}
Ok(out)
}
pub(crate) fn resolve_line_specs(
parsed: &[(bool, crate::text::RangeSpec)],
total_lines: usize,
) -> LineSpecs {
let mut specs = LineSpecs::default();
for (is_explicit, spec) in parsed {
let (lo, hi) = spec.resolve(total_lines, true);
if *is_explicit {
specs.explicit.insert(lo); } else {
specs.ranges.push((lo, hi));
}
}
specs
}
pub(crate) fn count_lines(file: &std::path::Path) -> Result<usize> {
let Some(mmap) = mmap_bytes(file)? else {
return Ok(0);
};
let bytes: &[u8] = &mmap;
Ok(memchr::memchr_iter(b'\n', bytes).count()
+ usize::from(!bytes.is_empty() && !bytes.ends_with(b"\n")))
}
pub(crate) fn resolve_single_transcript(target: &std::path::Path) -> Result<PathBuf> {
let files = path::resolve_session_files(
std::slice::from_ref(&target.to_path_buf()),
SubagentScope::TopLevelOnly,
path::Caller::Other,
)?;
match files.as_slice() {
[one] => Ok(one.clone()),
many => bail!(
"show targets exactly ONE transcript — '{}' resolves to {}. Name one: \
`@<uuid>` (a top-level session) | `@<agent-id>` (a subagent, ids from \
`csift agents`) | a `*.jsonl` path.",
target.display(),
many.len()
),
}
}
pub(crate) const DEFAULT_SHOW_CAP: usize = 200;
pub(crate) fn effective_cap(max_count: Option<usize>) -> usize {
match max_count {
Some(0) => usize::MAX,
Some(n) => n,
None => DEFAULT_SHOW_CAP,
}
}
pub(crate) fn turn_spec_is_explicit(spec: &crate::text::RangeSpec) -> bool {
matches!(spec.start, crate::text::Endpoint::At(_))
&& matches!(spec.end, crate::text::Endpoint::At(_))
}
pub(crate) fn turn_miss_error(spec: &crate::text::RangeSpec, turn_count: usize) -> anyhow::Error {
use crate::text::Endpoint;
let shown = match (spec.start, spec.end) {
(Endpoint::At(a), Endpoint::At(b)) if a == b => format!("t{a}"),
(Endpoint::At(a), Endpoint::At(b)) => format!("t{a}..t{b}"),
_ => "the requested turn range".to_string(),
};
let domain = if turn_count == 0 {
"the transcript has 0 turns".to_string()
} else {
format!(
"the transcript has {turn_count} turn(s) (t0..t{})",
turn_count - 1
)
};
anyhow::anyhow!(
"no such turn(s): {shown} — {domain}; turn indices are 0-based (the `tN` search \
prints), and the last k turns are `--turn -k..`"
)
}