Skip to main content

libsurfer/
variable_name_type.rs

1use derive_more::{Display, FromStr};
2use enum_iterator::Sequence;
3use itertools::Itertools;
4use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7
8use crate::displayed_item::DisplayedItemRef;
9use crate::displayed_item_tree::{Node, VisibleItemIndex};
10use crate::message::MessageTarget;
11use crate::wave_container::{ScopeRefExt, VariableRefExt};
12use crate::{displayed_item::DisplayedItem, wave_container::VariableRef, wave_data::WaveData};
13
14#[derive(PartialEq, Copy, Clone, Debug, Deserialize, Display, FromStr, Serialize, Sequence)]
15pub enum VariableNameType {
16    /// Local variable name only (i.e. for tb.dut.clk => clk)
17    Local,
18
19    /// Add unique prefix, prefix + local
20    Unique,
21
22    /// Full variable name (i.e. tb.dut.clk => tb.dut.clk)
23    Global,
24}
25
26const ELLIPSIS: &str = "…";
27
28impl WaveData {
29    pub fn compute_variable_display_names(&mut self) {
30        // First pass: collect all unique variable refs
31        let full_names: Vec<&VariableRef> = self
32            .items_tree
33            .iter()
34            .filter_map(|node| {
35                self.displayed_items
36                    .get(&node.item_ref)
37                    .and_then(|item| match item {
38                        DisplayedItem::Variable(variable) => Some(&variable.variable_ref),
39                        _ => None,
40                    })
41            })
42            .unique()
43            .collect();
44        // Compute minimal unique display names for collision groups.
45        let minimal_map = compute_minimal_display_map(&full_names);
46
47        // Single pass: update display names for all items using the precomputed map
48        for Node { item_ref, .. } in self.items_tree.iter() {
49            self.displayed_items
50                .entry(*item_ref)
51                .and_modify(|item| match item {
52                    DisplayedItem::Variable(variable) => {
53                        variable.display_name = match variable.display_name_type {
54                            VariableNameType::Local => variable.variable_ref.name.clone(),
55                            VariableNameType::Global => variable.variable_ref.full_path_string(),
56                            VariableNameType::Unique => minimal_map
57                                .get(&variable.variable_ref.full_path_string())
58                                .cloned()
59                                .unwrap_or_else(|| variable.variable_ref.name.clone()),
60                        };
61                        if self.display_variable_indices {
62                            let index = self
63                                .inner
64                                .as_waves()
65                                .unwrap()
66                                .variable_meta(&variable.variable_ref)
67                                .ok()
68                                .as_ref()
69                                .and_then(|meta| meta.index)
70                                .map(|index| format!(" {index}"))
71                                .unwrap_or_default();
72                            variable.display_name = format!("{}{}", variable.display_name, index);
73                        }
74                    }
75                    DisplayedItem::Divider(_) => {}
76                    DisplayedItem::Marker(_) => {}
77                    DisplayedItem::TimeLine(_) => {}
78                    DisplayedItem::Placeholder(_) => {}
79                    DisplayedItem::Stream(_) => {}
80                    DisplayedItem::Group(_) => {}
81                });
82        }
83    }
84
85    pub fn force_variable_name_type(&mut self, name_type: VariableNameType) {
86        for Node { item_ref, .. } in self.items_tree.iter() {
87            self.displayed_items.entry(*item_ref).and_modify(|item| {
88                if let DisplayedItem::Variable(variable) = item {
89                    variable.display_name_type = name_type;
90                }
91            });
92        }
93        self.default_variable_name_type = name_type;
94        self.compute_variable_display_names();
95    }
96
97    pub fn change_variable_name_type(
98        &mut self,
99        target: MessageTarget<VisibleItemIndex>,
100        name_type: VariableNameType,
101    ) -> bool {
102        let mut recompute_names = false;
103        let mut change_type = |item_ref: DisplayedItemRef| {
104            self.displayed_items.entry(item_ref).and_modify(|item| {
105                if let DisplayedItem::Variable(variable) = item {
106                    variable.display_name_type = name_type;
107                    recompute_names = true;
108                }
109            });
110        };
111
112        match target {
113            MessageTarget::Explicit(vidx) => {
114                if let Some(item_ref) = self.items_tree.get_visible(vidx).map(|node| node.item_ref)
115                {
116                    change_type(item_ref);
117                }
118            }
119            MessageTarget::CurrentSelection => {
120                self.items_tree
121                    .iter_visible_selected()
122                    .for_each(|node| change_type(node.item_ref));
123                self.focused_item
124                    .and_then(|vidx| self.items_tree.get_visible(vidx))
125                    .map(|node| node.item_ref)
126                    .map(change_type);
127            }
128        }
129        recompute_names
130    }
131}
132
133/// Compute minimal unique display names for a set of variables.
134/// Returns a map from `full_path_string()` -> minimal display name.
135fn compute_minimal_display_map(all_variables: &[&VariableRef]) -> HashMap<String, String> {
136    // Group variables by their local name: only collisions within the same
137    // local name need disambiguation.
138    let mut groups: HashMap<String, Vec<&VariableRef>> = HashMap::new();
139    for v in all_variables {
140        groups.entry(v.name.clone()).or_default().push(v);
141    }
142
143    let mut result: HashMap<String, String> = HashMap::with_capacity(all_variables.len());
144
145    for (_local, vars) in groups {
146        if vars.len() == 1 {
147            let v = vars[0];
148            result.insert(v.full_path_string(), v.name.clone());
149            continue;
150        }
151
152        // Build reversed scope component vectors for sorting and comparison.
153        let mut entries: Vec<(Vec<String>, &VariableRef)> = vars
154            .iter()
155            .map(|v| {
156                let rev: Vec<String> = v.path.strs().iter().rev().cloned().collect();
157                (rev, *v)
158            })
159            .collect();
160
161        entries.sort_by(|a, b| a.0.cmp(&b.0));
162
163        // Helper to compute common prefix length of two reversed component vectors.
164        fn common_prefix_len(a: &[String], b: &[String]) -> usize {
165            a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
166        }
167
168        for (i, (path_i, var_i)) in entries.iter().enumerate() {
169            let mut need = 0usize;
170            if i > 0 {
171                let common = common_prefix_len(path_i, &entries[i - 1].0);
172                need = need.max(common + 1);
173            }
174            if i + 1 < entries.len() {
175                let common = common_prefix_len(path_i, &entries[i + 1].0);
176                need = need.max(common + 1);
177            }
178
179            // If need is zero, fallback to local name.
180            if need == 0 || path_i.is_empty() {
181                result.insert(var_i.full_path_string(), var_i.name.clone());
182                continue;
183            }
184
185            // Take up to `need` reversed components, then reverse them back for display.
186            let take = need.min(path_i.len());
187            let scope = path_i.iter().take(take).rev().cloned().join(".");
188            let prefix = if take < path_i.len() { ELLIPSIS } else { "" };
189
190            let display = if scope.is_empty() {
191                var_i.name.clone()
192            } else {
193                format!("{}{}.{}", prefix, scope, var_i.name)
194            };
195            result.insert(var_i.full_path_string(), display);
196        }
197    }
198
199    result
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn minimal_display_map_unique_locals() {
208        let v1 = VariableRef::from_hierarchy_string("top.a");
209        let v2 = VariableRef::from_hierarchy_string("top.b");
210        let vars = vec![&v1, &v2];
211        let map = compute_minimal_display_map(&vars);
212        assert_eq!(
213            map.get(&v1.full_path_string())
214                .map(std::string::String::as_str),
215            Some("a")
216        );
217        assert_eq!(
218            map.get(&v2.full_path_string())
219                .map(std::string::String::as_str),
220            Some("b")
221        );
222    }
223
224    #[test]
225    fn minimal_display_map_collisions() {
226        let v1 = VariableRef::from_hierarchy_string("top.dut.x");
227        let v2 = VariableRef::from_hierarchy_string("other.dut.x");
228        let v3 = VariableRef::from_hierarchy_string("top.sub.x");
229        let vars = vec![&v1, &v2, &v3];
230        let map = compute_minimal_display_map(&vars);
231
232        assert_eq!(
233            map.get(&v1.full_path_string())
234                .map(std::string::String::as_str),
235            Some("top.dut.x")
236        );
237        assert_eq!(
238            map.get(&v2.full_path_string())
239                .map(std::string::String::as_str),
240            Some("other.dut.x")
241        );
242        assert_eq!(
243            map.get(&v3.full_path_string())
244                .map(std::string::String::as_str),
245            Some(ELLIPSIS.to_owned() + "sub.x").as_deref()
246        );
247    }
248
249    #[test]
250    fn minimal_display_map_root_and_scoped() {
251        let v1 = VariableRef::from_hierarchy_string("x");
252        let v2 = VariableRef::from_hierarchy_string("a.x");
253        let vars = vec![&v1, &v2];
254        let map = compute_minimal_display_map(&vars);
255        assert_eq!(
256            map.get(&v1.full_path_string())
257                .map(std::string::String::as_str),
258            Some("x")
259        );
260        assert_eq!(
261            map.get(&v2.full_path_string())
262                .map(std::string::String::as_str),
263            Some("a.x")
264        );
265    }
266}