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    ///
17    /// `symlink_mappings` maps symlink paths to their canonical targets.
18    /// This allows decorations on canonical paths to also appear under symlink aliases.
19    pub fn rebuild<I>(
20        decorations: I,
21        root: &Path,
22        symlink_mappings: &HashMap<PathBuf, PathBuf>,
23    ) -> Self
24    where
25        I: IntoIterator<Item = FileExplorerDecoration>,
26    {
27        let mut direct = HashMap::new();
28        for decoration in decorations {
29            if !decoration.path.starts_with(root) {
30                continue;
31            }
32            insert_best(&mut direct, decoration.clone());
33
34            // Also insert under symlink aliases
35            // If decoration.path = /real_dir/file.txt and symlink_mappings has
36            // /link_dir -> /real_dir, insert under /link_dir/file.txt too
37            for (symlink_path, canonical_target) in symlink_mappings {
38                if let Ok(suffix) = decoration.path.strip_prefix(canonical_target) {
39                    let aliased_path = symlink_path.join(suffix);
40                    insert_best(
41                        &mut direct,
42                        FileExplorerDecoration {
43                            path: aliased_path,
44                            symbol: decoration.symbol.clone(),
45                            color: decoration.color,
46                            priority: decoration.priority,
47                        },
48                    );
49                }
50            }
51        }
52
53        let mut bubbled = HashMap::new();
54        for (path, decoration) in &direct {
55            for ancestor in path.ancestors() {
56                if !ancestor.starts_with(root) {
57                    break;
58                }
59                insert_best(
60                    &mut bubbled,
61                    FileExplorerDecoration {
62                        path: ancestor.to_path_buf(),
63                        symbol: decoration.symbol.clone(),
64                        color: decoration.color,
65                        priority: decoration.priority,
66                    },
67                );
68            }
69        }
70
71        Self { direct, bubbled }
72    }
73
74    /// Lookup a decoration for an exact path.
75    pub fn direct_for_path(&self, path: &Path) -> Option<&FileExplorerDecoration> {
76        self.direct.get(path)
77    }
78
79    /// Lookup a bubbled decoration for a path (direct or descendant).
80    pub fn bubbled_for_path(&self, path: &Path) -> Option<&FileExplorerDecoration> {
81        self.bubbled.get(path)
82    }
83}
84
85fn insert_best(
86    map: &mut HashMap<PathBuf, FileExplorerDecoration>,
87    decoration: FileExplorerDecoration,
88) {
89    let replace = match map.get(&decoration.path) {
90        Some(existing) => decoration.priority >= existing.priority,
91        None => true,
92    };
93
94    if replace {
95        map.insert(decoration.path.clone(), decoration);
96    }
97}