#[cfg(feature = "ast")]
fn sparse_outline(
args: &SparseOutlineArgs,
global: &GlobalArgs,
writer: &mut NdjsonWriter<impl Write>,
shutdown: &ShutdownSignal,
) -> Result<()> {
use crate::concurrency::sort_paths_parallel;
use std::time::Instant;
let start = Instant::now();
let workspace = global.resolve_workspace()?;
let root = validate_path(&args.path, &workspace)?;
crate::walk_globs::validate(&args.include, &[])?;
let walker = crate::commands::build_scoped_walker_flavored(
std::slice::from_ref(&root),
global,
crate::commands::WalkerFlavor::AlwaysVisitHidden,
&[],
&args.exclude,
)?;
let include = args.include.clone();
let failures = std::sync::Arc::new(std::sync::Mutex::new(
crate::ndjson_types::FailureTally::default(),
));
crate::concurrency::record_ignore_file_failures(
std::slice::from_ref(&root),
Some(&failures),
);
let (mut paths, hit_budget) = collect_mapped_parallel_budgeted_tallied(
&walker,
args.max_files,
move |entry| {
if crate::signal::is_global_shutdown() {
return None;
}
if !entry.file_type().is_some_and(|ft| ft.is_file()) {
return None;
}
let path = entry.path().to_path_buf();
if !include.is_empty() {
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
let ok = include.iter().any(|pat| {
if let Some(suf) = pat.strip_prefix("*.") {
name.ends_with(suf)
|| path.extension().and_then(|e| e.to_str()) == Some(suf)
} else {
name.contains(pat) || path.to_string_lossy().contains(pat)
}
});
if !ok {
return None;
}
}
Some(path)
},
Some(std::sync::Arc::clone(&failures)),
);
sort_paths_parallel(&mut paths);
let mut truncated = hit_budget || paths.len() as u64 > args.max_files;
if paths.len() as u64 > args.max_files {
paths.truncate(args.max_files as usize);
}
if crate::output::cap_collected(&mut paths) {
truncated = true;
}
let files_seen = paths.len() as u64;
let max = global.effective_max_filesize();
let record_read = |path: &PathBuf, e: &anyhow::Error| {
if let Ok(mut tally) = failures.lock() {
tally.record(
path,
anyhow::Error::new(crate::error::AtomwriteError::Io {
source: std::io::Error::other(e.to_string()),
}),
);
}
};
let outlined: Vec<(PathBuf, Vec<crate::commands::outline::OutlineItem>, bool)> =
if should_parallelize(paths.len()) {
paths
.par_iter()
.filter_map(|path| {
if shutdown.is_shutdown() {
return None;
}
let content = match crate::file_io::read_file_bytes(path, max) {
Ok(content) => content,
Err(e) => {
record_read(path, &e);
return None;
}
};
let (items, parsed) = crate::commands::outline::collect_outline_for_path_status(
path, &content, None,
);
Some((path.clone(), items, parsed))
})
.collect()
} else {
paths
.iter()
.filter_map(|path| {
if shutdown.is_shutdown() {
return None;
}
let content = match crate::file_io::read_file_bytes(path, max) {
Ok(content) => content,
Err(e) => {
record_read(path, &e);
return None;
}
};
let (items, parsed) = crate::commands::outline::collect_outline_for_path_status(
path, &content, None,
);
Some((path.clone(), items, parsed))
})
.collect()
};
let mut outlined = outlined;
outlined.sort_by(|a, b| a.0.cmp(&b.0));
let mut items_emitted = 0u64;
let mut items_truncated = 0u64;
let mut files_unparsed = 0u64;
for (_, items, parsed) in outlined {
if !parsed {
files_unparsed += 1;
}
items_emitted += items.len() as u64;
items_truncated += items
.iter()
.filter(|i| i.signature_truncated == Some(true))
.count() as u64;
for item in items {
writer.write_event(&item)?;
}
}
let tally = match std::sync::Arc::try_unwrap(failures) {
Ok(mutex) => mutex.into_inner().unwrap_or_default(),
Err(arc) => std::mem::take(&mut *arc.lock().unwrap_or_else(|e| e.into_inner())),
};
let (count, items, first) = tally.into_parts();
let files_failed = (count > 0).then_some(count);
let summary = SparseSummary {
r#type: "sparse_summary",
emitted: writer.emitted_count(),
truncated,
elapsed_ms: start.elapsed().as_millis() as u64,
files_failed,
partial: crate::ndjson_types::partial_of(files_failed),
errors: (!items.is_empty()).then_some(items),
walker_stopped_early: crate::output::limit_reached().then_some(true),
files_scanned: Some(files_seen),
path_bytes: None,
files_budget_dropped: Some(crate::output::budget_dropped()).filter(|n| *n > 0),
files_unparsed: (files_unparsed > 0).then_some(files_unparsed),
items_truncated: (items_truncated > 0).then_some(items_truncated),
};
if let Some(err) = first {
return Err(writer.publish_failure(&summary, err));
}
writer.write_terminal(&summary)?;
writer.write_event(&crate::ndjson_types::SparseOutlineBudget {
r#type: "sparse_outline_budget",
files_seen,
items: items_emitted,
truncated,
max_files: args.max_files,
})?;
Ok(())
}