libsurfer/
variable_name_type.rs1use 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,
18
19 Unique,
21
22 Global,
24}
25
26const ELLIPSIS: &str = "…";
27
28impl WaveData {
29 pub fn compute_variable_display_names(&mut self) {
30 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 let minimal_map = compute_minimal_display_map(&full_names);
46
47 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
133fn compute_minimal_display_map(all_variables: &[&VariableRef]) -> HashMap<String, String> {
136 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 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 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 == 0 || path_i.is_empty() {
181 result.insert(var_i.full_path_string(), var_i.name.clone());
182 continue;
183 }
184
185 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}