Skip to main content

codetether_agent/tui/utils/
workspace_entries.rs

1//! FS directory scanning + sorting for workspace snapshots.
2
3use std::path::Path;
4
5use super::workspace_helpers::should_skip_entry;
6use super::workspace_types::{WorkspaceEntry, WorkspaceEntryKind};
7
8pub fn collect_entries(root: &Path) -> Vec<WorkspaceEntry> {
9    let Ok(rd) = std::fs::read_dir(root) else {
10        return Vec::new();
11    };
12    rd.flatten()
13        .filter_map(|entry| {
14            // `into_string()` is a zero-copy conversion when the name is
15            // valid UTF-8 (the common case on every platform we support);
16            // fall back to `to_string_lossy` only when it isn't.
17            let name = entry
18                .file_name()
19                .into_string()
20                .unwrap_or_else(|os| os.to_string_lossy().into_owned());
21            if should_skip_entry(&name) {
22                return None;
23            }
24            let kind = match entry.file_type() {
25                Ok(ft) if ft.is_dir() => WorkspaceEntryKind::Directory,
26                _ => WorkspaceEntryKind::File,
27            };
28            Some(WorkspaceEntry { name, kind })
29        })
30        .collect()
31}
32
33pub fn sort_entries(entries: &mut [WorkspaceEntry]) {
34    // Case-insensitive compare WITHOUT allocating a lowercased String
35    // per comparison. On a workspace with many entries the old code
36    // produced O(n log n) allocations of filename-sized strings; this
37    // does zero allocations regardless of entry count.
38    fn cmp_ascii_ci(a: &str, b: &str) -> std::cmp::Ordering {
39        let ab = a.as_bytes();
40        let bb = b.as_bytes();
41        let len = ab.len().min(bb.len());
42        for i in 0..len {
43            let av = ab[i].to_ascii_lowercase();
44            let bv = bb[i].to_ascii_lowercase();
45            if av != bv {
46                return av.cmp(&bv);
47            }
48        }
49        ab.len().cmp(&bb.len())
50    }
51    entries.sort_by(|a, b| match (a.kind, b.kind) {
52        (WorkspaceEntryKind::Directory, WorkspaceEntryKind::File) => std::cmp::Ordering::Less,
53        (WorkspaceEntryKind::File, WorkspaceEntryKind::Directory) => std::cmp::Ordering::Greater,
54        _ => cmp_ascii_ci(&a.name, &b.name),
55    });
56}