Skip to main content

fresh/view/file_tree/
decorations.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4// Re-export from fresh-core for shared type usage
5pub use fresh_core::file_explorer::FileExplorerDecoration;
6
7/// Cached decoration lookups for file explorer rendering.
8#[derive(Debug, Default, Clone)]
9pub struct FileExplorerDecorationCache {
10    direct: HashMap<PathBuf, FileExplorerDecoration>,
11    bubbled: HashMap<PathBuf, FileExplorerDecoration>,
12}
13
14impl FileExplorerDecorationCache {
15    /// Rebuild the cache from a list of decorations.
16    pub fn rebuild<I>(decorations: I, root: &Path) -> Self
17    where
18        I: IntoIterator<Item = FileExplorerDecoration>,
19    {
20        let mut direct = HashMap::new();
21        for decoration in decorations {
22            if !decoration.path.starts_with(root) {
23                continue;
24            }
25            insert_best(&mut direct, decoration);
26        }
27
28        let mut bubbled = HashMap::new();
29        for (path, decoration) in &direct {
30            for ancestor in path.ancestors() {
31                if !ancestor.starts_with(root) {
32                    break;
33                }
34                insert_best(
35                    &mut bubbled,
36                    FileExplorerDecoration {
37                        path: ancestor.to_path_buf(),
38                        symbol: decoration.symbol.clone(),
39                        color: decoration.color,
40                        priority: decoration.priority,
41                    },
42                );
43            }
44        }
45
46        Self { direct, bubbled }
47    }
48
49    /// Lookup a decoration for an exact path.
50    /// Also tries the canonicalized path to handle symlinks.
51    pub fn direct_for_path(&self, path: &Path) -> Option<&FileExplorerDecoration> {
52        self.direct.get(path).or_else(|| {
53            // Try canonicalized path for symlink support
54            path.canonicalize().ok().and_then(|p| self.direct.get(&p))
55        })
56    }
57
58    /// Lookup a bubbled decoration for a path (direct or descendant).
59    /// Also tries the canonicalized path to handle symlinks.
60    pub fn bubbled_for_path(&self, path: &Path) -> Option<&FileExplorerDecoration> {
61        self.bubbled.get(path).or_else(|| {
62            // Try canonicalized path for symlink support
63            path.canonicalize().ok().and_then(|p| self.bubbled.get(&p))
64        })
65    }
66}
67
68fn insert_best(
69    map: &mut HashMap<PathBuf, FileExplorerDecoration>,
70    decoration: FileExplorerDecoration,
71) {
72    let replace = match map.get(&decoration.path) {
73        Some(existing) => decoration.priority >= existing.priority,
74        None => true,
75    };
76
77    if replace {
78        map.insert(decoration.path.clone(), decoration);
79    }
80}