Skip to main content

libsurfer/
lib.rs

1#![deny(unused_crate_dependencies)]
2
3pub mod analog_renderer;
4pub mod analog_signal_cache;
5pub mod async_util;
6pub mod batch_commands;
7#[cfg(feature = "performance_plot")]
8pub mod benchmark;
9mod channels;
10pub mod clock_highlighting;
11pub mod command_parser;
12pub mod command_prompt;
13pub mod config;
14pub mod cxxrtl;
15pub mod cxxrtl_container;
16pub mod data_container;
17pub mod dialog;
18pub mod displayed_item;
19pub mod displayed_item_tree;
20pub mod drawing_canvas;
21pub mod file_dialog;
22pub mod file_watcher;
23pub mod fzcmd;
24pub mod graphics;
25pub mod help;
26pub mod hierarchy;
27pub mod keyboard_shortcuts;
28pub mod keys;
29pub mod logs;
30pub mod marker;
31pub mod menus;
32pub mod message;
33pub mod mousegestures;
34pub mod overview;
35pub mod remote;
36pub mod server_file_window;
37pub mod state;
38pub mod state_file_io;
39pub mod state_util;
40pub mod statusbar;
41pub mod system_state;
42#[cfg(test)]
43pub mod tests;
44pub mod time;
45pub mod toolbar;
46pub mod tooltips;
47pub mod transaction_container;
48pub mod transactions;
49pub mod translation;
50pub mod util;
51pub mod variable_direction;
52pub mod variable_filter;
53mod variable_index;
54pub mod variable_meta;
55pub mod variable_name_type;
56pub mod view;
57pub mod viewport;
58#[cfg(target_arch = "wasm32")]
59pub mod wasm_api;
60#[cfg(target_arch = "wasm32")]
61pub mod wasm_panic;
62pub mod wave_container;
63pub mod wave_data;
64pub mod wave_source;
65pub mod wcp;
66pub mod wellen;
67
68use crate::config::AutoLoad;
69use crate::displayed_item_tree::ItemIndex;
70use crate::displayed_item_tree::TargetPosition;
71use crate::remote::get_time_table_from_server;
72use crate::variable_name_type::VariableNameType;
73use std::collections::HashMap;
74use std::sync::atomic::{AtomicUsize, Ordering};
75use std::sync::mpsc::{self, Receiver, Sender};
76use std::sync::{Arc, LazyLock, RwLock};
77
78use batch_commands::read_command_bytes;
79use batch_commands::read_command_file;
80#[cfg(target_arch = "wasm32")]
81use channels::{GlobalChannelTx, IngressHandler, IngressReceiver};
82use derive_more::Display;
83use displayed_item::DisplayedVariable;
84use displayed_item_tree::DisplayedItemTree;
85use eframe::{App, CreationContext};
86use egui::{FontData, FontDefinitions, FontFamily};
87use eyre::Context;
88use eyre::Result;
89use ftr_parser::types::Transaction;
90use futures::executor::block_on;
91use itertools::Itertools;
92use message::MessageTarget;
93use num::BigInt;
94use serde::Deserialize;
95use surfer_translation_types::Translator;
96use surfer_wcp::{WcpCSMessage, WcpEvent, WcpSCMessage};
97pub use system_state::SystemState;
98#[cfg(target_arch = "wasm32")]
99use tokio_stream as _;
100use tracing::{error, info, warn};
101#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
102use translation::wasm_translator::PluginTranslator;
103use wave_container::ScopeRef;
104
105#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
106use crate::async_util::perform_work;
107use crate::config::{SurferConfig, SurferTheme};
108use crate::dialog::{OpenSiblingStateFileDialog, ReloadWaveformDialog};
109use crate::displayed_item::{
110    AnalogVarState, DisplayedFieldRef, DisplayedItem, DisplayedItemRef, FieldFormat,
111};
112use crate::displayed_item_tree::VisibleItemIndex;
113use crate::drawing_canvas::TxDrawingCommands;
114use crate::message::Message;
115use crate::transaction_container::{StreamScopeRef, TransactionRef, TransactionStreamRef};
116use crate::translation::{AnyTranslator, all_translators};
117use crate::variable_filter::{VariableIOFilterType, VariableNameFilterType};
118use crate::viewport::Viewport;
119use crate::wave_container::VariableRefExt;
120use crate::wave_container::{ScopeRefExt, WaveContainer};
121use crate::wave_data::{ScopeType, WaveData};
122use crate::wave_source::{LoadOptions, WaveFormat, WaveSource};
123use crate::wellen::{HeaderResult, convert_format};
124
125/// A number that is non-zero if there are asynchronously triggered operations that
126/// have been triggered but not successfully completed yet. In practice, if this is
127/// non-zero, we will re-run the egui update function in order to ensure that we deal
128/// with the outstanding transactions eventually.
129/// When incrementing this, it is important to make sure that it gets decremented
130/// whenever the asynchronous transaction is completed, otherwise we will re-render
131/// things until program exit
132pub(crate) static OUTSTANDING_TRANSACTIONS: AtomicUsize = AtomicUsize::new(0);
133
134pub static EGUI_CONTEXT: LazyLock<RwLock<Option<Arc<egui::Context>>>> =
135    LazyLock::new(|| RwLock::new(None));
136
137#[cfg(target_arch = "wasm32")]
138pub(crate) static WCP_CS_HANDLER: LazyLock<IngressHandler<WcpCSMessage>> =
139    LazyLock::new(IngressHandler::new);
140
141#[cfg(target_arch = "wasm32")]
142pub(crate) static WCP_SC_HANDLER: LazyLock<GlobalChannelTx<WcpSCMessage>> =
143    LazyLock::new(GlobalChannelTx::new);
144
145#[derive(Default)]
146pub struct StartupParams {
147    pub waves: Option<WaveSource>,
148    pub wcp_initiate: Option<u16>,
149    pub startup_commands: Vec<String>,
150}
151
152fn setup_custom_font(ctx: &egui::Context) {
153    let mut fonts = FontDefinitions::default();
154
155    fonts.font_data.insert(
156        "remix_icons".to_owned(),
157        FontData::from_static(egui_remixicon::FONT).into(),
158    );
159
160    fonts
161        .families
162        .get_mut(&FontFamily::Proportional)
163        .unwrap()
164        .push("remix_icons".to_owned());
165
166    fonts
167        .families
168        .get_mut(&FontFamily::Monospace)
169        .unwrap()
170        .push("remix_icons".to_owned());
171
172    ctx.set_fonts(fonts);
173}
174
175pub fn run_egui(cc: &CreationContext, mut state: SystemState) -> Result<Box<dyn App>> {
176    let ctx_arc = Arc::new(cc.egui_ctx.clone());
177    *EGUI_CONTEXT.write().unwrap() = Some(ctx_arc.clone());
178    state.context = Some(ctx_arc.clone());
179    cc.egui_ctx
180        .set_visuals_of(egui::Theme::Dark, state.get_visuals());
181    cc.egui_ctx
182        .set_visuals_of(egui::Theme::Light, state.get_visuals());
183    cc.egui_ctx.all_styles_mut(|style| {
184        if state.user.config.animation_time == 0.0 {
185            info!("With animation_time set to 0.0, animations cannot be enabled.");
186        }
187        style.animation_time = if state.user.config.animation_enabled() {
188            state.user.config.animation_time
189        } else {
190            0.0
191        };
192    });
193    #[cfg(not(target_arch = "wasm32"))]
194    if state.user.config.wcp.autostart {
195        state.start_wcp_server(Some(state.user.config.wcp.address.clone()), false);
196    }
197    setup_custom_font(&cc.egui_ctx);
198    Ok(Box::new(state))
199}
200
201#[derive(Debug, Clone, Copy, Deserialize, Display, PartialEq, Eq)]
202pub enum MoveDir {
203    #[display("up")]
204    Up,
205
206    #[display("down")]
207    Down,
208}
209
210pub enum ColorSpecifier {
211    Index(usize),
212    Name(String),
213}
214
215enum CachedDrawData {
216    WaveDrawData(CachedWaveDrawData),
217    TransactionDrawData(CachedTransactionDrawData),
218}
219
220struct CachedWaveDrawData {
221    pub draw_commands: HashMap<DisplayedFieldRef, drawing_canvas::DrawingCommands>,
222    pub clock_edges: Vec<f32>,
223    pub ticks: Vec<(String, f32)>,
224}
225
226struct CachedTransactionDrawData {
227    pub draw_commands: HashMap<TransactionRef, TxDrawingCommands>,
228    pub stream_to_displayed_txs: HashMap<TransactionStreamRef, Vec<TransactionRef>>,
229    pub inc_relation_tx_ids: Vec<TransactionRef>,
230    pub out_relation_tx_ids: Vec<TransactionRef>,
231}
232
233pub struct Channels {
234    pub msg_sender: Sender<Message>,
235    pub msg_receiver: Receiver<Message>,
236    #[cfg(target_arch = "wasm32")]
237    wcp_c2s_receiver: Option<IngressReceiver<WcpCSMessage>>,
238    #[cfg(not(target_arch = "wasm32"))]
239    wcp_c2s_receiver: Option<tokio::sync::mpsc::Receiver<WcpCSMessage>>,
240    wcp_s2c_sender: Option<tokio::sync::mpsc::Sender<WcpSCMessage>>,
241}
242impl Channels {
243    fn new() -> Self {
244        let (msg_sender, msg_receiver) = mpsc::channel();
245        Self {
246            msg_sender,
247            msg_receiver,
248            wcp_c2s_receiver: None,
249            wcp_s2c_sender: None,
250        }
251    }
252}
253
254pub struct WcpClientCapabilities {
255    pub waveforms_loaded: bool,
256    pub goto_declaration: bool,
257    pub add_drivers: bool,
258    pub add_loads: bool,
259}
260impl WcpClientCapabilities {
261    fn new() -> Self {
262        Self {
263            waveforms_loaded: false,
264            goto_declaration: false,
265            add_drivers: false,
266            add_loads: false,
267        }
268    }
269}
270
271/// Stores the current canvas state to enable undo/redo operations
272struct CanvasState {
273    message: String,
274    focused_item: Option<VisibleItemIndex>,
275    focused_transaction: (Option<TransactionRef>, Option<Transaction>),
276    items_tree: DisplayedItemTree,
277    displayed_items: HashMap<DisplayedItemRef, DisplayedItem>,
278    markers: HashMap<u8, BigInt>,
279}
280
281impl SystemState {
282    pub fn update(&mut self, message: Message) -> Option<()> {
283        if tracing::enabled!(tracing::Level::TRACE)
284            && !matches!(message, Message::CommandPromptUpdate { .. })
285        {
286            tracing::trace!("{message:?}");
287        }
288        match message {
289            Message::SetActiveScope(scope) => {
290                let waves = self.user.waves.as_mut()?;
291                if let Some(scope) = scope {
292                    let scope = if let ScopeType::StreamScope(StreamScopeRef::Empty(name)) = scope {
293                        ScopeType::StreamScope(StreamScopeRef::new_stream_from_name(
294                            waves.inner.as_transactions().unwrap(),
295                            name,
296                        ))
297                    } else {
298                        scope
299                    };
300
301                    if waves.inner.scope_exists(&scope) {
302                        waves.active_scope = Some(scope);
303                    } else {
304                        warn!("Setting active scope to {scope} which does not exist");
305                    }
306                } else {
307                    // Set to top-level scope
308                    waves.active_scope = None;
309                }
310            }
311            Message::ExpandScope(scope_ref) => {
312                *self.scope_ref_to_expand.borrow_mut() = Some(scope_ref);
313            }
314            Message::AddVariables(vars) => {
315                if !vars.is_empty() {
316                    let undo_msg = if vars.len() == 1 {
317                        format!("Add variable {}", vars[0].name)
318                    } else {
319                        format!("Add {} variables", vars.len())
320                    };
321                    self.save_current_canvas(undo_msg);
322                    if let Some(waves) = self.user.waves.as_mut() {
323                        if let (Some(cmd), _) =
324                            waves.add_variables(&self.translators, vars, None, true, false, None)
325                        {
326                            self.load_variables(cmd);
327                        }
328                        self.invalidate_draw_commands();
329                    } else {
330                        error!("Could not load signals, no waveform loaded");
331                    }
332                }
333            }
334            Message::AddDivider(name, vidx) => {
335                self.save_current_canvas("Add divider".into());
336                let waves = self.user.waves.as_mut()?;
337                waves.add_divider(name, vidx);
338            }
339            Message::AddTimeLine(vidx) => {
340                self.save_current_canvas("Add timeline".into());
341                let waves = self.user.waves.as_mut()?;
342                waves.add_timeline(vidx);
343            }
344            Message::AddScope(scope, recursive) => {
345                self.save_current_canvas(format!("Add scope {}", scope.name()));
346
347                let vars = self.get_scope(scope, recursive);
348                let waves = self.user.waves.as_mut()?;
349
350                // TODO add parameter to add_variables, insert to (self.drag_target_idx, self.drag_source_idx)
351                if let (Some(cmd), _) =
352                    waves.add_variables(&self.translators, vars, None, true, false, None)
353                {
354                    self.load_variables(cmd);
355                }
356
357                self.invalidate_draw_commands();
358            }
359            Message::AddScopeAsGroup(scope, recursive) => {
360                self.save_current_canvas(format!("Add scope {} as group", scope.name()));
361                let waves = self.user.waves.as_mut()?;
362                let passed_or_focused = waves.insert_position(waves.focused_item);
363                let target = passed_or_focused.unwrap_or_else(|| waves.end_insert_position());
364
365                self.add_scope_as_group(&scope, target, recursive, None);
366                self.invalidate_draw_commands();
367
368                self.user.waves.as_mut()?.compute_variable_display_names();
369            }
370            Message::AddCount(digit) => {
371                if let Some(count) = &mut self.user.count {
372                    count.push(digit);
373                } else {
374                    self.user.count = Some(digit.to_string());
375                }
376            }
377            Message::AddStreamOrGenerator(s) => {
378                let undo_msg = if let Some(gen_id) = s.gen_id {
379                    format!("Add generator(id: {gen_id})")
380                } else {
381                    format!("Add stream(id: {})", s.stream_id)
382                };
383                self.save_current_canvas(undo_msg);
384
385                let waves = self.user.waves.as_mut()?;
386                if s.gen_id.is_some() {
387                    waves.add_generator(s);
388                } else {
389                    waves.add_stream(s);
390                }
391                self.invalidate_draw_commands();
392            }
393            Message::AddStreamOrGeneratorFromName(scope, name) => {
394                self.save_current_canvas(format!("Add Stream/Generator from name: {}", &name));
395                let waves = self.user.waves.as_mut()?;
396                let inner = waves.inner.as_transactions()?;
397                match scope {
398                    Some(StreamScopeRef::Root) => {
399                        let (stream_id, name) = inner
400                            .get_stream_from_name(name)
401                            .map(|s| (s.id, s.name.clone()))
402                            .unwrap();
403
404                        waves.add_stream(TransactionStreamRef::new_stream(stream_id, name));
405                    }
406                    Some(StreamScopeRef::Stream(stream)) => {
407                        let (stream_id, id, name) = inner
408                            .get_generator_from_name(Some(stream.stream_id), name)
409                            .map(|g| (g.stream_id, g.id, g.name.clone()))
410                            .unwrap();
411
412                        waves.add_generator(TransactionStreamRef::new_gen(stream_id, id, name));
413                    }
414                    Some(StreamScopeRef::Empty(_)) => {}
415                    None => {
416                        let (stream_id, id, name) = inner
417                            .get_generator_from_name(None, name)
418                            .map(|g| (g.stream_id, g.id, g.name.clone()))
419                            .unwrap();
420
421                        waves.add_generator(TransactionStreamRef::new_gen(stream_id, id, name));
422                    }
423                }
424                self.invalidate_draw_commands();
425            }
426            Message::AddAllFromStreamScope(scope_name) => {
427                self.save_current_canvas(format!("Add all from scope {}", scope_name.clone()));
428                let waves = self.user.waves.as_mut()?;
429                if scope_name == "tr" {
430                    waves.add_all_streams();
431                } else {
432                    let inner = waves.inner.as_transactions()?;
433                    let stream = inner.get_stream_from_name(scope_name)?;
434                    let gens = stream
435                        .generators
436                        .iter()
437                        .map(|gen_id| inner.get_generator(*gen_id).unwrap())
438                        .map(|g| (g.stream_id, g.id, g.name.clone()))
439                        .collect_vec();
440
441                    for (stream_id, id, name) in gens {
442                        waves.add_generator(TransactionStreamRef::new_gen(
443                            stream_id,
444                            id,
445                            name.clone(),
446                        ));
447                    }
448                }
449                self.invalidate_draw_commands();
450            }
451            Message::InvalidateCount => self.user.count = None,
452            Message::SetNameAlignRight(align_right) => {
453                self.user.align_names_right = Some(align_right);
454            }
455            Message::FocusItem(idx) => {
456                let waves = self.user.waves.as_mut()?;
457
458                let visible_items_len = waves.displayed_items.len();
459                if idx.0 < visible_items_len {
460                    waves.focused_item = Some(idx);
461                } else {
462                    error!(
463                        "Can not focus variable {} because only {visible_items_len} variables are visible.",
464                        idx.0
465                    );
466                }
467            }
468            Message::ItemSelectRange(select_to) => {
469                let waves = self.user.waves.as_mut()?;
470                let select_from = waves.focused_item?;
471                waves
472                    .items_tree
473                    .xselect_visible_range(select_from, select_to, true);
474            }
475            Message::ItemSelectAll => {
476                let waves = self.user.waves.as_mut()?;
477                waves.items_tree.xselect_all_visible(true);
478            }
479            Message::SetItemSelected(vidx, selected) => {
480                let waves = self.user.waves.as_mut()?;
481                waves.items_tree.xselect(vidx, selected);
482            }
483            Message::ToggleItemSelected(vidx) => {
484                let waves = self.user.waves.as_mut()?;
485                let node = vidx
486                    .or(waves.focused_item)
487                    .and_then(|vidx| waves.items_tree.to_displayed(vidx))
488                    .and_then(|item| waves.items_tree.get_mut(item))?;
489                node.selected = !node.selected;
490            }
491            Message::SetDefaultTimeline(v) => {
492                self.user.show_default_timeline = Some(v);
493            }
494            Message::UnfocusItem => {
495                let waves = self.user.waves.as_mut()?;
496                waves.focused_item = None;
497            }
498            Message::MoveFocus(direction, count, select) => {
499                let waves = self.user.waves.as_mut()?;
500                let visible_item_cnt = waves.items_tree.iter_visible().count();
501                if visible_item_cnt == 0 {
502                    return None;
503                }
504
505                let new_focus_vidx = VisibleItemIndex(match direction {
506                    MoveDir::Up => waves
507                        .focused_item
508                        .map_or(visible_item_cnt, |vidx| vidx.0)
509                        .saturating_sub(count),
510                    MoveDir::Down => waves
511                        .focused_item
512                        .map_or(usize::MAX, |vidx| vidx.0)
513                        .wrapping_add(count)
514                        .clamp(0, visible_item_cnt - 1),
515                });
516
517                if select {
518                    if let Some(vidx) = waves.focused_item {
519                        waves.items_tree.xselect(vidx, true);
520                    }
521                    waves.items_tree.xselect(new_focus_vidx, true);
522                }
523                waves.focused_item = Some(new_focus_vidx);
524            }
525            Message::FocusTransaction(tx_ref, tx) => {
526                if let Some(tx_ref) = tx_ref.as_ref()
527                    && tx.is_none()
528                {
529                    self.save_current_canvas(format!("Focus Transaction id: {}", tx_ref.id));
530                }
531                let waves = self.user.waves.as_mut()?;
532                let invalidate = tx.is_none();
533                waves.focused_transaction =
534                    (tx_ref, tx.or_else(|| waves.focused_transaction.1.clone()));
535                if invalidate {
536                    self.invalidate_draw_commands();
537                }
538            }
539            Message::ScrollToItem(position) => {
540                let waves = self.user.waves.as_mut()?;
541                waves.scroll_to_item(position);
542            }
543            Message::SetScrollOffset(offset) => {
544                let waves = self.user.waves.as_mut()?;
545                waves.scroll_offset = offset;
546            }
547            Message::SetLogsVisible(visibility) => self.user.show_logs = visibility,
548            Message::SetCursorWindowVisible(visibility) => {
549                self.user.show_cursor_window = visibility;
550            }
551            Message::VerticalScroll(direction, count) => {
552                let waves = self.user.waves.as_mut()?;
553                let current_item = waves.get_top_item();
554                match direction {
555                    MoveDir::Down => {
556                        waves.scroll_to_item(current_item + count);
557                    }
558                    MoveDir::Up => {
559                        if current_item > count {
560                            waves.scroll_to_item(current_item - count);
561                        } else {
562                            waves.scroll_to_item(0);
563                        }
564                    }
565                }
566            }
567            Message::SetSurverFileWindowVisible(visibility) => {
568                self.user.show_server_file_window = visibility;
569            }
570            Message::LoadSurverFileByIndex(file_index, load_options) => {
571                // Disable file window in case executing from command/test
572                self.user.show_server_file_window = false;
573                let force_switch = self.user.selected_server_file_index != file_index;
574                self.user.selected_server_file_index = file_index;
575                *self.surver_selected_file.borrow_mut() = file_index;
576                if let Some(url) = self.user.surver_url.as_ref() {
577                    self.load_wave_from_url(url.to_string(), load_options, force_switch);
578                }
579            }
580            Message::LoadSurverFileByName(file_name, load_options) => {
581                // Disable file window in case executing from command/test
582                self.user.show_server_file_window = false;
583                let file_index = self
584                    .user
585                    .surver_file_infos
586                    .as_ref()?
587                    .iter()
588                    .position(|fi| fi.filename == file_name);
589                let force_switch = self.user.selected_server_file_index != file_index;
590
591                self.user.selected_server_file_index = file_index;
592                *self.surver_selected_file.borrow_mut() = file_index;
593                if let Some(url) = self.user.surver_url.as_ref() {
594                    self.load_wave_from_url(url.to_string(), load_options, force_switch);
595                }
596            }
597            Message::RemoveVisibleItems(target) => match target {
598                MessageTarget::Explicit(vidx) => {
599                    let waves = self.user.waves.as_ref();
600                    let item_ref = waves
601                        .and_then(|waves| waves.items_tree.get_visible(vidx))
602                        .map(|node| node.item_ref);
603                    let undo_msg = item_ref
604                        .and_then(|item_ref| {
605                            waves.and_then(|waves| waves.displayed_items.get(&item_ref))
606                        })
607                        .map(displayed_item::DisplayedItem::name)
608                        .map_or("Remove one item".to_string(), |name| {
609                            format!("Remove item {name}")
610                        });
611                    self.save_current_canvas(undo_msg);
612
613                    if let Some(waves) = self.user.waves.as_mut()
614                        && let Some(item_ref) = item_ref
615                    {
616                        waves.remove_displayed_item(item_ref);
617                    }
618                }
619                MessageTarget::CurrentSelection => {
620                    self.save_current_canvas("Remove selected items".to_owned());
621                    let waves = self.user.waves.as_mut()?;
622
623                    let mut remove_ids: Vec<_> = waves
624                        .items_tree
625                        .iter_visible_selected()
626                        .map(|node| node.item_ref)
627                        .collect();
628                    if let Some(node) = waves
629                        .focused_item
630                        .and_then(|focus| waves.items_tree.get_visible(focus))
631                    {
632                        remove_ids.push(node.item_ref);
633                    }
634                    for &item_ref in remove_ids.iter() {
635                        waves.remove_displayed_item(item_ref);
636                    }
637                }
638            },
639            Message::RemoveItems(items) => {
640                let undo_msg = self
641                    .user
642                    .waves
643                    .as_ref()
644                    .and_then(|waves| {
645                        if items.len() == 1 {
646                            items.first().and_then(|item_ref| {
647                                waves
648                                    .displayed_items
649                                    .get(item_ref)
650                                    .map(|item| format!("Remove item {}", item.name()))
651                            })
652                        } else {
653                            Some(format!("Remove {} items", items.len()))
654                        }
655                    })
656                    .unwrap_or_default();
657                self.save_current_canvas(undo_msg);
658
659                let waves = self.user.waves.as_mut()?;
660                for id in items.iter().sorted_unstable_by(|a, b| Ord::cmp(b, a)) {
661                    waves.remove_displayed_item(*id);
662                }
663            }
664            Message::MoveFocusedItem(direction, count) => {
665                self.save_current_canvas(format!("Move item {direction}, {count}"));
666                self.invalidate_draw_commands();
667                let waves = self.user.waves.as_mut()?;
668                let mut vidx = waves.focused_item?;
669                for _ in 0..count {
670                    vidx = waves
671                        .items_tree
672                        .move_item(vidx, direction, |node| {
673                            matches!(
674                                waves.displayed_items.get(&node.item_ref),
675                                Some(DisplayedItem::Group(..))
676                            )
677                        })
678                        .expect("move failed for unknown reason");
679                }
680                waves.focused_item = waves.focused_item.and(Some(vidx));
681            }
682            Message::CanvasScroll {
683                delta,
684                viewport_idx,
685            } => {
686                let waves = self.user.waves.as_mut()?;
687                waves.viewports[viewport_idx]
688                    .handle_canvas_scroll(f64::from(delta.y) + f64::from(delta.x));
689                self.invalidate_draw_commands();
690            }
691            Message::CanvasZoom {
692                delta,
693                mouse_ptr,
694                viewport_idx,
695            } => {
696                let waves = self.user.waves.as_mut()?;
697                if let Some(num_timestamps) = waves.num_timestamps() {
698                    waves.viewports[viewport_idx].handle_canvas_zoom(
699                        mouse_ptr,
700                        f64::from(delta),
701                        &num_timestamps,
702                    );
703                    self.invalidate_draw_commands();
704                } else {
705                    warn!(
706                        "Canvas zoom: No timestamps count, even though waveforms should be loaded"
707                    );
708                }
709            }
710            Message::ZoomToFit { viewport_idx } => {
711                let waves = self.user.waves.as_mut()?;
712                waves.viewports[viewport_idx].zoom_to_fit();
713                self.invalidate_draw_commands();
714            }
715            Message::GoToEnd { viewport_idx } => {
716                let waves = self.user.waves.as_mut()?;
717                waves.viewports[viewport_idx].go_to_end();
718                self.invalidate_draw_commands();
719            }
720            Message::GoToStart { viewport_idx } => {
721                let waves = self.user.waves.as_mut()?;
722                waves.viewports[viewport_idx].go_to_start();
723                self.invalidate_draw_commands();
724            }
725            Message::GoToTime(time, viewport_idx) => {
726                let waves = self.user.waves.as_mut()?;
727                // If there are no timestamps, the file is not fully loaded
728                if let Some(num_timestamps) = waves.num_timestamps() {
729                    let time = time?;
730                    waves.viewports[viewport_idx].go_to_time(&time.clone(), &num_timestamps);
731                    self.invalidate_draw_commands();
732                } else {
733                    warn!(
734                        "Go to time: No timestamps count, even though waveforms should be loaded"
735                    );
736                }
737            }
738            Message::SetTimeUnit(timeunit) => {
739                self.user.wanted_timeunit = timeunit;
740                self.invalidate_draw_commands();
741            }
742            Message::SetTimeStringFormatting(format) => {
743                self.user.time_string_format = format;
744                self.invalidate_draw_commands();
745            }
746            Message::ZoomToRange {
747                start,
748                end,
749                viewport_idx,
750            } => {
751                let waves = self.user.waves.as_mut()?;
752                // If there are no timestamps, the file is not fully loaded
753                if let Some(num_timestamps) = waves.num_timestamps() {
754                    waves.viewports[viewport_idx].zoom_to_range(&start, &end, &num_timestamps);
755                    self.invalidate_draw_commands();
756                } else {
757                    warn!(
758                        "Zoom to range: No timestamps count, even though waveforms should be loaded"
759                    );
760                }
761            }
762            Message::VariableFormatChange(displayed_field_ref, format) => {
763                let waves = self.user.waves.as_mut()?;
764                if !self
765                    .translators
766                    .all_translator_names()
767                    .contains(&format.as_str())
768                {
769                    warn!("No translator {format}");
770                    return None;
771                }
772
773                let update_format =
774                    |variable: &mut DisplayedVariable, field_ref: DisplayedFieldRef| {
775                        if field_ref.field.is_empty() {
776                            let Ok(meta) = waves
777                                .inner
778                                .as_waves()
779                                .unwrap()
780                                .variable_meta(&variable.variable_ref)
781                                .map_err(|e| {
782                                    warn!("Error trying to get variable metadata: {e:#?}");
783                                })
784                            else {
785                                return;
786                            };
787                            let translator = self.translators.get_translator(&format);
788                            let new_info = translator.variable_info(&meta).unwrap();
789
790                            variable.format = Some(format.clone());
791                            variable.info = new_info;
792                        } else {
793                            variable
794                                .field_formats
795                                .retain(|ff| ff.field != field_ref.field);
796                            variable.field_formats.push(FieldFormat {
797                                field: field_ref.field,
798                                format: format.clone(),
799                            });
800                        }
801                    };
802
803                // convert focused item index to item ref
804                let focused = waves
805                    .focused_item
806                    .and_then(|vidx| waves.items_tree.get_visible(vidx))
807                    .map(|node| node.item_ref);
808
809                let mut redraw = false;
810
811                match displayed_field_ref {
812                    MessageTarget::Explicit(field_ref) => {
813                        if let Some(DisplayedItem::Variable(displayed_variable)) =
814                            waves.displayed_items.get_mut(&field_ref.item)
815                        {
816                            update_format(displayed_variable, field_ref);
817                            redraw = true;
818                        }
819                    }
820                    MessageTarget::CurrentSelection => {
821                        //If an item is focused, update its format too
822                        if let Some(focused) = focused
823                            && let Some(DisplayedItem::Variable(displayed_variable)) =
824                                waves.displayed_items.get_mut(&focused)
825                        {
826                            update_format(displayed_variable, DisplayedFieldRef::from(focused));
827                            redraw = true;
828                        }
829                        for item in waves
830                            .items_tree
831                            .iter_visible_selected()
832                            .map(|node| node.item_ref)
833                        {
834                            //Update format for all selected
835                            let field_ref = DisplayedFieldRef::from(item);
836                            if let Some(DisplayedItem::Variable(variable)) =
837                                waves.displayed_items.get_mut(&item)
838                            {
839                                update_format(variable, field_ref);
840                            }
841                            redraw = true;
842                        }
843                    }
844                }
845
846                if redraw {
847                    self.invalidate_draw_commands();
848                }
849            }
850            Message::ItemSelectionClear => {
851                let waves = self.user.waves.as_mut()?;
852                waves.items_tree.xselect_all_visible(false);
853            }
854            Message::ItemColorChange(vidx, color_name) => {
855                self.save_current_canvas(format!(
856                    "Change item color to {}",
857                    color_name.clone().unwrap_or("default".into())
858                ));
859                self.invalidate_draw_commands();
860                let waves = self.user.waves.as_mut()?;
861
862                match vidx {
863                    MessageTarget::Explicit(vidx) => {
864                        let node = waves.items_tree.get_visible(vidx)?;
865                        waves
866                            .displayed_items
867                            .entry(node.item_ref)
868                            .and_modify(|item| item.set_color(&color_name));
869                    }
870                    MessageTarget::CurrentSelection => {
871                        if let Some(focused) = waves.focused_item {
872                            let node = waves.items_tree.get_visible(focused)?;
873                            waves
874                                .displayed_items
875                                .entry(node.item_ref)
876                                .and_modify(|item| item.set_color(&color_name));
877                        }
878
879                        for node in waves.items_tree.iter_visible_selected() {
880                            waves
881                                .displayed_items
882                                .entry(node.item_ref)
883                                .and_modify(|item| item.set_color(&color_name));
884                        }
885                    }
886                }
887            }
888            Message::ItemNameChange(vidx, name) => {
889                self.save_current_canvas(format!(
890                    "Change item name to {}",
891                    name.clone().unwrap_or("default".into())
892                ));
893                let waves = self.user.waves.as_mut()?;
894                let vidx = vidx.or(waves.focused_item)?;
895                let node = waves.items_tree.get_visible(vidx)?;
896                waves
897                    .displayed_items
898                    .entry(node.item_ref)
899                    .and_modify(|item| item.set_name(name));
900            }
901            Message::ItemNameReset(target) => {
902                self.save_current_canvas("Resetting item name(s)".to_owned());
903                let waves = self.user.waves.as_mut()?;
904                match target {
905                    MessageTarget::Explicit(vidx) => {
906                        let node = waves.items_tree.get_visible(vidx)?;
907                        waves
908                            .displayed_items
909                            .entry(node.item_ref)
910                            .and_modify(|item| item.set_name(None));
911                    }
912                    MessageTarget::CurrentSelection => {
913                        for node in waves.items_tree.iter_visible_selected() {
914                            waves
915                                .displayed_items
916                                .entry(node.item_ref)
917                                .and_modify(|item| item.set_name(None));
918                        }
919                    }
920                }
921            }
922            Message::ItemBackgroundColorChange(vidx, color_name) => {
923                self.save_current_canvas(format!(
924                    "Change item background color to {}",
925                    color_name.clone().unwrap_or("default".into())
926                ));
927                let waves = self.user.waves.as_mut()?;
928
929                match vidx {
930                    MessageTarget::Explicit(vidx) => {
931                        let node = waves.items_tree.get_visible(vidx)?;
932                        waves
933                            .displayed_items
934                            .entry(node.item_ref)
935                            .and_modify(|item| item.set_background_color(&color_name));
936                    }
937                    MessageTarget::CurrentSelection => {
938                        if let Some(focused) = waves.focused_item {
939                            let node = waves.items_tree.get_visible(focused)?;
940                            waves
941                                .displayed_items
942                                .entry(node.item_ref)
943                                .and_modify(|item| item.set_background_color(&color_name));
944                        }
945
946                        for node in waves.items_tree.iter_visible_selected() {
947                            waves
948                                .displayed_items
949                                .entry(node.item_ref)
950                                .and_modify(|item| item.set_background_color(&color_name));
951                        }
952                    }
953                }
954            }
955            Message::ItemHeightScalingFactorChange(vidx, scale) => {
956                self.save_current_canvas(format!("Change item height scaling factor to {scale}"));
957                let waves = self.user.waves.as_mut()?;
958
959                match vidx {
960                    MessageTarget::Explicit(vidx) => {
961                        let node = waves.items_tree.get_visible(vidx)?;
962                        waves
963                            .displayed_items
964                            .entry(node.item_ref)
965                            .and_modify(|item| item.set_height_scaling_factor(scale));
966                    }
967                    MessageTarget::CurrentSelection => {
968                        if let Some(focused) = waves.focused_item {
969                            let node = waves.items_tree.get_visible(focused)?;
970                            waves
971                                .displayed_items
972                                .entry(node.item_ref)
973                                .and_modify(|item| item.set_height_scaling_factor(scale));
974                        }
975
976                        for node in waves.items_tree.iter_visible_selected() {
977                            waves
978                                .displayed_items
979                                .entry(node.item_ref)
980                                .and_modify(|item| item.set_height_scaling_factor(scale));
981                        }
982                    }
983                }
984            }
985            Message::SetAnalogSettings(vidx, new_settings) => {
986                self.save_current_canvas("Set analog state".into());
987                self.invalidate_draw_commands();
988                let waves = self.user.waves.as_mut()?;
989
990                // Update settings while preserving existing cache
991                let update = |item: &mut DisplayedItem| {
992                    if let DisplayedItem::Variable(var) = item {
993                        match (&mut var.analog, new_settings) {
994                            (Some(s), Some(new)) => s.settings = new,
995                            (None, Some(new)) => var.analog = Some(AnalogVarState::new(new)),
996                            (_, None) => var.analog = None,
997                        }
998                    }
999                };
1000
1001                match vidx {
1002                    MessageTarget::Explicit(vidx) => {
1003                        let node = waves.items_tree.get_visible(vidx)?;
1004                        waves
1005                            .displayed_items
1006                            .entry(node.item_ref)
1007                            .and_modify(update);
1008                    }
1009                    MessageTarget::CurrentSelection => {
1010                        if let Some(focused) = waves.focused_item {
1011                            let node = waves.items_tree.get_visible(focused)?;
1012                            waves
1013                                .displayed_items
1014                                .entry(node.item_ref)
1015                                .and_modify(update);
1016                        }
1017                        for node in waves.items_tree.iter_visible_selected() {
1018                            waves
1019                                .displayed_items
1020                                .entry(node.item_ref)
1021                                .and_modify(update);
1022                        }
1023                    }
1024                }
1025            }
1026            Message::MoveCursorToTransition {
1027                next,
1028                variable,
1029                skip_zero,
1030            } => {
1031                let waves = self.user.waves.as_mut()?;
1032                // If there are no timestamps, the file is not fully loaded
1033                if let Some(num_timestamps) = waves.num_timestamps() {
1034                    // if no cursor is set, move it to
1035                    // start of visible area transition for next transition
1036                    // end of visible area for previous transition
1037                    if waves.cursor.is_none()
1038                        && waves.focused_item.is_some()
1039                        && let Some(vp) = waves.viewports.first()
1040                    {
1041                        waves.cursor = if next {
1042                            Some(vp.left_edge_time(&num_timestamps))
1043                        } else {
1044                            Some(vp.right_edge_time(&num_timestamps))
1045                        };
1046                    }
1047                    waves.set_cursor_at_transition(next, variable, skip_zero);
1048                    let moved = waves.go_to_cursor_if_not_in_view();
1049                    if moved {
1050                        self.invalidate_draw_commands();
1051                    }
1052                } else {
1053                    warn!(
1054                        "Move cursor to transition: No timestamps count, even though waveforms should be loaded"
1055                    );
1056                }
1057            }
1058            Message::MoveTransaction { next } => {
1059                let undo_msg = if next {
1060                    "Move to next transaction"
1061                } else {
1062                    "Move to previous transaction"
1063                };
1064                self.save_current_canvas(undo_msg.to_string());
1065                let waves = self.user.waves.as_mut()?;
1066                let inner = waves.inner.as_transactions()?;
1067                let mut transactions = waves
1068                    .items_tree
1069                    .iter_visible()
1070                    .flat_map(|node| {
1071                        let item = &waves.displayed_items[&node.item_ref];
1072                        match item {
1073                            DisplayedItem::Stream(s) => {
1074                                let stream_ref = &s.transaction_stream_ref;
1075                                let stream_id = stream_ref.stream_id;
1076                                if let Some(gen_id) = stream_ref.gen_id {
1077                                    inner.get_transactions_from_generator(gen_id)
1078                                } else {
1079                                    inner.get_transactions_from_stream(stream_id)
1080                                }
1081                            }
1082                            _ => vec![],
1083                        }
1084                    })
1085                    .collect_vec();
1086
1087                transactions.sort_unstable();
1088                let tx = if let Some(focused_tx) = &waves.focused_transaction.0 {
1089                    let next_id = transactions
1090                        .iter()
1091                        .enumerate()
1092                        .find(|(_, tx)| **tx == focused_tx.id)
1093                        .map_or(
1094                            if next { transactions.len() - 1 } else { 0 },
1095                            |(vec_idx, _)| {
1096                                if next {
1097                                    if vec_idx + 1 < transactions.len() {
1098                                        vec_idx + 1
1099                                    } else {
1100                                        transactions.len() - 1
1101                                    }
1102                                } else {
1103                                    vec_idx.saturating_sub(1)
1104                                }
1105                            },
1106                        );
1107                    Some(TransactionRef {
1108                        id: *transactions.get(next_id).unwrap(),
1109                    })
1110                } else if !transactions.is_empty() {
1111                    Some(TransactionRef {
1112                        id: *transactions.first().unwrap(),
1113                    })
1114                } else {
1115                    None
1116                };
1117                waves.focused_transaction = (tx, waves.focused_transaction.1.clone());
1118
1119                self.invalidate_draw_commands();
1120            }
1121            Message::ResetVariableFormat(displayed_field_ref) => {
1122                let waves = self.user.waves.as_mut()?;
1123                if let Some(DisplayedItem::Variable(displayed_variable)) =
1124                    waves.displayed_items.get_mut(&displayed_field_ref.item)
1125                {
1126                    if displayed_field_ref.field.is_empty() {
1127                        displayed_variable.format = None;
1128                    } else {
1129                        displayed_variable
1130                            .field_formats
1131                            .retain(|ff| ff.field != displayed_field_ref.field);
1132                    }
1133                    self.invalidate_draw_commands();
1134                }
1135            }
1136            Message::CursorSet(time) => {
1137                let waves = self.user.waves.as_mut()?;
1138                waves.cursor = Some(time);
1139            }
1140            Message::ExpandParameterSection => {
1141                self.expand_parameter_section = true;
1142            }
1143            Message::LoadFile(filename, load_options) => {
1144                self.user.selected_server_file_index = None;
1145                *self.surver_selected_file.borrow_mut() = None;
1146                #[cfg(not(target_arch = "wasm32"))]
1147                self.load_from_file(filename, load_options).ok();
1148                #[cfg(target_arch = "wasm32")]
1149                error!("Cannot load file from path in WASM");
1150            }
1151            Message::LoadWaveformFileFromUrl(url, load_options) => {
1152                self.user.selected_server_file_index = None;
1153                *self.surver_selected_file.borrow_mut() = None;
1154                // If we provide URL at command line and it is a Surver URL, we want to force a switch
1155                self.load_wave_from_url(url, load_options, true);
1156            }
1157            Message::LoadFromData(data, load_options) => {
1158                self.user.selected_server_file_index = None;
1159                *self.surver_selected_file.borrow_mut() = None;
1160                self.load_from_data(data, load_options).ok();
1161            }
1162            #[cfg(feature = "python")]
1163            Message::LoadPythonTranslator(filename) => {
1164                try_log_error!(
1165                    self.translators.load_python_translator(filename),
1166                    "Error loading Python translator",
1167                )
1168            }
1169            #[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
1170            Message::LoadWasmTranslator(path) => {
1171                let sender = self.channels.msg_sender.clone();
1172                perform_work(
1173                    move || match PluginTranslator::new(path.into_std_path_buf()) {
1174                        Ok(t) => {
1175                            if let Err(e) = sender.send(Message::TranslatorLoaded(Arc::new(t))) {
1176                                error!("Failed to send message: {e}");
1177                            }
1178                        }
1179                        Err(e) => {
1180                            error!("Failed to load wasm translator {e:#}");
1181                        }
1182                    },
1183                );
1184            }
1185            Message::LoadCommandFile(path) => {
1186                self.add_batch_commands(read_command_file(&path));
1187            }
1188            Message::LoadCommandFileFromUrl(url) => {
1189                self.load_commands_from_url(url);
1190            }
1191            Message::LoadCommandFromData(bytes) => {
1192                self.add_batch_commands(read_command_bytes(bytes));
1193            }
1194            Message::SetupCxxrtl(kind) => self.connect_to_cxxrtl(kind, false),
1195            Message::SetSurverStatus(_start, server, status) => {
1196                self.user.surver_file_infos = Some(status.file_infos.clone());
1197                info!(
1198                    "Received surfer server status from {server}. {} files available.",
1199                    status.file_infos.len()
1200                );
1201                self.user.surver_url = Some(server.clone());
1202                if status.file_infos.is_empty() {
1203                    warn!("Received surfer server status with no file infos");
1204                    return None;
1205                }
1206                if self.user.selected_server_file_index.is_none() {
1207                    if status.file_infos.len() == 1 {
1208                        // if only one file is available, select it automatically
1209                        self.user.selected_server_file_index = Some(0);
1210                        *self.surver_selected_file.borrow_mut() = Some(0);
1211                        info!(
1212                            "Only one file available on server {}, loading it automatically",
1213                            server
1214                        );
1215                        self.load_wave_from_url(server.clone(), LoadOptions::Clear, false);
1216                    } else {
1217                        // if no file is selected, show the server file selection window
1218                        self.user.show_server_file_window = true;
1219                        self.progress_tracker = None;
1220                    }
1221                }
1222
1223                if let Some(file_index) = self.user.selected_server_file_index {
1224                    if file_index >= status.file_infos.len() {
1225                        warn!(
1226                            "Selected server file index {file_index} is out of bounds ({} files available)",
1227                            status.file_infos.len()
1228                        );
1229                        return None;
1230                    }
1231                    self.server_status_to_progress(&server, &status.file_infos[file_index]);
1232                }
1233            }
1234            Message::FileDropped(dropped_file) => {
1235                self.load_from_dropped(dropped_file)
1236                    .map_err(|e| error!("{e:#?}"))
1237                    .ok();
1238            }
1239            Message::StopProgressTracker => {
1240                self.progress_tracker = None;
1241            }
1242            Message::WaveHeaderLoaded(start, source, load_options, header) => {
1243                // for files using the `wellen` backend, we load the header before parsing the body
1244                info!(
1245                    "Loaded the hierarchy and meta-data of {source} in {:?}",
1246                    start.elapsed()
1247                );
1248                match header {
1249                    HeaderResult::LocalFile(header) => {
1250                        // register waveform as loaded (but with no variable info yet!)
1251                        let shared_hierarchy = Arc::new(header.hierarchy);
1252                        let new_waves =
1253                            Box::new(WaveContainer::new_waveform(shared_hierarchy.clone()));
1254                        self.on_waves_loaded(
1255                            source.clone(),
1256                            convert_format(header.file_format),
1257                            new_waves,
1258                            load_options,
1259                        );
1260                        // start parsing of the body
1261                        self.load_wave_body(source, header.body, header.body_len, shared_hierarchy);
1262                    }
1263                    HeaderResult::LocalBytes(header) => {
1264                        // register waveform as loaded (but with no variable info yet!)
1265                        let shared_hierarchy = Arc::new(header.hierarchy);
1266                        let new_waves =
1267                            Box::new(WaveContainer::new_waveform(shared_hierarchy.clone()));
1268                        self.on_waves_loaded(
1269                            source.clone(),
1270                            convert_format(header.file_format),
1271                            new_waves,
1272                            load_options,
1273                        );
1274                        // start parsing of the body
1275                        self.load_wave_body(source, header.body, header.body_len, shared_hierarchy);
1276                    }
1277                    HeaderResult::Remote(hierarchy, file_format, server, file_index) => {
1278                        // register waveform as loaded (but with no variable info yet!)
1279                        let new_waves = Box::new(WaveContainer::new_remote_waveform(
1280                            &server,
1281                            hierarchy.clone(),
1282                        ));
1283                        self.on_waves_loaded(
1284                            source.clone(),
1285                            convert_format(file_format),
1286                            new_waves,
1287                            load_options,
1288                        );
1289                        // body is already being parsed on the server, we need to request the time table though
1290                        get_time_table_from_server(
1291                            self.channels.msg_sender.clone(),
1292                            server,
1293                            file_index,
1294                        );
1295                    }
1296                }
1297            }
1298            Message::WaveBodyLoaded(start, source, body) => {
1299                // for files using the `wellen` backend, parse the body in a second step
1300                info!("Loaded the body of {source} in {:?}", start.elapsed());
1301                self.progress_tracker = None;
1302                let waves = self
1303                    .user
1304                    .waves
1305                    .as_mut()
1306                    .expect("Waves should be loaded at this point!");
1307                // add source and time table
1308                let maybe_cmd = waves // TODO
1309                    .inner
1310                    .as_waves_mut()
1311                    .unwrap()
1312                    .wellen_add_body(body)
1313                    .map_err(|err| {
1314                        error!("While getting commands to lazy-load signals: {err:?}");
1315                    })
1316                    .ok()
1317                    .flatten();
1318                // Pre-load parameters
1319                let param_cmd = waves
1320                    .inner
1321                    .as_waves_mut()
1322                    .unwrap()
1323                    .load_parameters()
1324                    .map_err(|err| {
1325                        error!("While getting commands to lazy-load parameters: {err:?}");
1326                    })
1327                    .ok()
1328                    .flatten();
1329
1330                if self.wcp_greeted_signal.load(Ordering::Relaxed)
1331                    && self.wcp_client_capabilities.waveforms_loaded
1332                {
1333                    let source = match source {
1334                        WaveSource::File(path) => path.to_string(),
1335                        WaveSource::Url(url) => url,
1336                        _ => String::new(),
1337                    };
1338                    self.channels.wcp_s2c_sender.as_ref().map(|ch| {
1339                        block_on(
1340                            ch.send(WcpSCMessage::event(WcpEvent::waveforms_loaded { source })),
1341                        )
1342                    });
1343                }
1344
1345                // update viewports, now that we have the time table
1346                waves.update_viewports();
1347                // make sure we redraw
1348                self.invalidate_draw_commands();
1349                // start loading parameters
1350                if let Some(cmd) = param_cmd {
1351                    self.load_variables(cmd);
1352                }
1353                // start loading variables
1354                if let Some(cmd) = maybe_cmd {
1355                    self.load_variables(cmd);
1356                }
1357            }
1358            Message::SignalsLoaded(start, res) => {
1359                info!("Loaded {} variables in {:?}", res.len(), start.elapsed());
1360                self.progress_tracker = None;
1361                let waves = self
1362                    .user
1363                    .waves
1364                    .as_mut()
1365                    .expect("Waves should be loaded at this point!");
1366                match waves.inner.as_waves_mut().unwrap().on_signals_loaded(res) {
1367                    Err(err) => error!("{err:?}"),
1368                    Ok(Some(cmd)) => self.load_variables(cmd),
1369                    _ => {}
1370                }
1371                // make sure we redraw since now more variable data is available
1372                self.invalidate_draw_commands();
1373            }
1374            Message::WavesLoaded(filename, format, new_waves, load_options) => {
1375                self.on_waves_loaded(filename, format, new_waves, load_options);
1376                // here, the body and thus the number of timestamps is already loaded!
1377                self.user
1378                    .waves
1379                    .as_mut()
1380                    .expect("Waves should be loaded at this point!")
1381                    .update_viewports();
1382                self.progress_tracker = None;
1383            }
1384            Message::TransactionStreamsLoaded(filename, format, new_ftr, loaded_options) => {
1385                self.on_transaction_streams_loaded(filename, format, new_ftr, loaded_options);
1386                self.user
1387                    .waves
1388                    .as_mut()
1389                    .expect("Waves should be loaded at this point!")
1390                    .update_viewports();
1391            }
1392            Message::BlacklistTranslator(idx, translator) => {
1393                self.user.blacklisted_translators.insert((idx, translator));
1394            }
1395            Message::Error(e) => {
1396                error!("{e:?}");
1397                self.user.show_logs = true;
1398            }
1399            Message::TranslatorLoaded(t) => {
1400                info!("Translator {} loaded", t.name());
1401                t.set_wave_source(
1402                    self.user
1403                        .waves
1404                        .as_ref()
1405                        .map(|waves| waves.source.into_translation_type()),
1406                );
1407
1408                self.translators.add_or_replace(AnyTranslator::Full(t));
1409            }
1410            Message::SetSidePanelVisible(v) => self.user.show_hierarchy = Some(v),
1411            Message::SetMenuVisible(v) => self.user.show_menu = Some(v),
1412            Message::ToggleMenu => {
1413                self.user.show_menu = Some(!self.show_menu());
1414            }
1415            Message::SetToolbarVisible(v) => self.user.show_toolbar = Some(v),
1416            Message::SetShowEmptyScopes(v) => self.user.show_empty_scopes = Some(v),
1417            Message::SetShowHierarchyIcons(v) => self.user.show_hierarchy_icons = Some(v),
1418            Message::SetParameterDisplayLocation(location) => {
1419                self.user.parameter_display_location = Some(location);
1420            }
1421            Message::SetStatusbarVisible(v) => self.user.show_statusbar = Some(v),
1422            Message::SetTickLines(v) => self.user.show_ticks = Some(v),
1423            Message::SetVariableTooltip(v) => self.user.show_tooltip = Some(v),
1424            Message::SetScopeTooltip(v) => self.user.show_scope_tooltip = Some(v),
1425            Message::SetOverviewVisible(v) => self.user.show_overview = Some(v),
1426            Message::SetShowVariableDirection(v) => self.user.show_variable_direction = Some(v),
1427            Message::SetTransitionValue(v) => self.user.transition_value = Some(v),
1428            Message::SetShowIndices(v) => {
1429                let new = v;
1430                self.user.show_variable_indices = Some(new);
1431                let waves = self.user.waves.as_mut()?;
1432                waves.display_variable_indices = new;
1433                waves.compute_variable_display_names();
1434            }
1435            Message::SetHighlightFocused(highlight) => {
1436                self.user.highlight_focused = Some(highlight);
1437            }
1438            Message::HideCommandPrompt => {
1439                *self.command_prompt_text.borrow_mut() = String::new();
1440                self.command_prompt.suggestions = vec![];
1441                self.command_prompt.selected = self.command_prompt.previous_commands.len();
1442                self.command_prompt.visible = false;
1443            }
1444            Message::ShowCommandPrompt(text, selected) => {
1445                self.command_prompt.new_text = Some((text, selected.unwrap_or(String::new())));
1446                self.command_prompt.visible = true;
1447            }
1448            Message::FileDownloaded(url, bytes, load_options) => {
1449                self.load_from_bytes(WaveSource::Url(url), bytes.to_vec(), load_options);
1450            }
1451            Message::CommandFileDownloaded(_url, bytes) => {
1452                self.add_batch_commands(read_command_bytes(bytes.to_vec()));
1453                self.progress_tracker = None;
1454            }
1455            Message::SetConfigFromString(s) => {
1456                // FIXME think about a structured way to collect errors
1457                let config = SurferConfig::new_from_toml(&s)
1458                    .with_context(|| "Failed to load config file")
1459                    .ok()?;
1460
1461                self.user.config = config;
1462
1463                let ctx = &self.context.as_ref()?;
1464                ctx.set_visuals(self.get_visuals());
1465            }
1466            Message::ReloadConfig => {
1467                // FIXME think about a structured way to collect errors
1468                let config = SurferConfig::new(false)
1469                    .with_context(|| "Failed to load config file")
1470                    .ok()?;
1471                self.translators = all_translators();
1472                self.user.config = config;
1473
1474                let ctx = &self.context.as_ref()?;
1475                ctx.set_visuals(self.get_visuals());
1476            }
1477            Message::ReloadWaveform(keep_unavailable) => {
1478                let waves = self.user.waves.as_ref()?;
1479                let options = if keep_unavailable {
1480                    LoadOptions::KeepAll
1481                } else {
1482                    LoadOptions::KeepAvailable
1483                };
1484                match &waves.source {
1485                    WaveSource::File(filename) => {
1486                        self.load_from_file(filename.clone(), options).ok();
1487                    }
1488                    WaveSource::Data => {}       // can't reload
1489                    WaveSource::Cxxrtl(..) => {} // can't reload
1490                    WaveSource::DragAndDrop(filename) => {
1491                        filename
1492                            .clone()
1493                            .and_then(|filename| self.load_from_file(filename, options).ok());
1494                    }
1495                    WaveSource::Url(url) => {
1496                        self.load_wave_from_url(url.clone(), options, false);
1497                    }
1498                }
1499
1500                for translator in self.translators.all_translators() {
1501                    translator.reload(self.channels.msg_sender.clone());
1502                }
1503                self.variable_name_info_cache.borrow_mut().clear();
1504            }
1505            Message::SuggestReloadWaveform => match self.autoreload_files() {
1506                AutoLoad::Always => self.update(Message::ReloadWaveform(true))?,
1507                AutoLoad::Never => (),
1508                AutoLoad::Ask => {
1509                    self.user.show_reload_suggestion = Some(ReloadWaveformDialog::default());
1510                }
1511            },
1512            Message::CloseReloadWaveformDialog {
1513                reload_file,
1514                do_not_show_again,
1515            } => {
1516                if do_not_show_again {
1517                    // FIXME: This is currently saved in state, but could be persisted in
1518                    // some setting.
1519                    self.user.autoreload_files = Some(AutoLoad::from_bool(reload_file));
1520                }
1521                self.user.show_reload_suggestion = None;
1522                if reload_file {
1523                    self.update(Message::ReloadWaveform(true));
1524                }
1525            }
1526            Message::UpdateReloadWaveformDialog(dialog) => {
1527                self.user.show_reload_suggestion = Some(dialog);
1528            }
1529            Message::OpenSiblingStateFile(open) => {
1530                if !open {
1531                    return None;
1532                }
1533                let waves = self.user.waves.as_ref()?;
1534                let state_file_path = waves.source.sibling_state_file()?;
1535                self.load_state_file(Some(state_file_path.clone().into_std_path_buf()));
1536            }
1537            Message::SuggestOpenSiblingStateFile => match self.autoload_sibling_state_files() {
1538                AutoLoad::Always => {
1539                    self.update(Message::OpenSiblingStateFile(true));
1540                }
1541                AutoLoad::Never => {}
1542                AutoLoad::Ask => {
1543                    self.user.show_open_sibling_state_file_suggestion =
1544                        Some(OpenSiblingStateFileDialog::default());
1545                }
1546            },
1547            Message::CloseOpenSiblingStateFileDialog {
1548                load_state,
1549                do_not_show_again,
1550            } => {
1551                if do_not_show_again {
1552                    self.user.autoload_sibling_state_files = Some(AutoLoad::from_bool(load_state));
1553                }
1554                self.user.show_open_sibling_state_file_suggestion = None;
1555                if load_state {
1556                    self.update(Message::OpenSiblingStateFile(true));
1557                }
1558            }
1559            Message::UpdateOpenSiblingStateFileDialog(dialog) => {
1560                self.user.show_open_sibling_state_file_suggestion = Some(dialog);
1561            }
1562            Message::RemovePlaceholders => {
1563                let waves = self.user.waves.as_mut()?;
1564                waves.remove_placeholders();
1565            }
1566            Message::SetClockHighlightType(new_type) => {
1567                self.user.clock_highlight_type = Some(new_type);
1568            }
1569            Message::SetFillHighValues(fill) => self.user.fill_high_values = Some(fill),
1570            Message::SetDinotraceStyle(dino_style) => {
1571                self.user.use_dinotrace_style = Some(dino_style);
1572                self.invalidate_draw_commands();
1573            }
1574            Message::AddMarker {
1575                time,
1576                name,
1577                move_focus,
1578            } => {
1579                if let Some(name) = &name {
1580                    self.save_current_canvas(format!("Add marker {name} at {time}"));
1581                } else {
1582                    self.save_current_canvas(format!("Add marker at {time}"));
1583                }
1584                let waves = self.user.waves.as_mut()?;
1585                waves.add_marker(&time, name, move_focus);
1586            }
1587            Message::SetMarker { id, time } => {
1588                self.save_current_canvas(format!("Set marker {id} to {time}"));
1589                let waves = self.user.waves.as_mut()?;
1590                waves.set_marker_position(id, &time);
1591            }
1592            Message::RemoveMarker(id) => {
1593                let waves = self.user.waves.as_mut()?;
1594                waves.remove_marker(id);
1595            }
1596            Message::MoveMarkerToCursor(idx) => {
1597                self.save_current_canvas("Move marker".into());
1598                let waves = self.user.waves.as_mut()?;
1599                waves.move_marker_to_cursor(idx);
1600            }
1601            Message::GoToCursorIfNotInView => {
1602                let waves = self.user.waves.as_mut()?;
1603                if waves.go_to_cursor_if_not_in_view() {
1604                    self.invalidate_draw_commands();
1605                }
1606            }
1607            Message::GoToMarkerPosition(idx, viewport_idx) => {
1608                let waves = self.user.waves.as_mut()?;
1609                // If there are no timestamps, the file is not fully loaded
1610                if let Some(num_timestamps) = waves.num_timestamps() {
1611                    let cursor = waves.markers.get(&idx)?;
1612                    waves.viewports[viewport_idx].go_to_time(cursor, &num_timestamps);
1613                    self.invalidate_draw_commands();
1614                } else {
1615                    warn!(
1616                        "Go to marker position: No timestamps count, even though waveforms should be loaded"
1617                    );
1618                }
1619            }
1620            Message::ChangeVariableNameType(target, name_type) => {
1621                let waves = self.user.waves.as_mut()?;
1622                let mut recompute_names = false;
1623                let mut change_type = |item_ref: DisplayedItemRef| {
1624                    waves.displayed_items.entry(item_ref).and_modify(|item| {
1625                        if let DisplayedItem::Variable(variable) = item {
1626                            variable.display_name_type = name_type;
1627                            recompute_names = true;
1628                        }
1629                    });
1630                };
1631
1632                match target {
1633                    MessageTarget::Explicit(vidx) => {
1634                        if let Some(item_ref) =
1635                            waves.items_tree.get_visible(vidx).map(|node| node.item_ref)
1636                        {
1637                            change_type(item_ref);
1638                        }
1639                    }
1640                    MessageTarget::CurrentSelection => {
1641                        waves
1642                            .items_tree
1643                            .iter_visible_selected()
1644                            .for_each(|node| change_type(node.item_ref));
1645                        waves
1646                            .focused_item
1647                            .and_then(|vidx| waves.items_tree.get_visible(vidx))
1648                            .map(|node| node.item_ref)
1649                            .map(change_type);
1650                    }
1651                }
1652
1653                if recompute_names {
1654                    waves.compute_variable_display_names();
1655                }
1656            }
1657            Message::ForceVariableNameTypes(name_type) => {
1658                let waves = self.user.waves.as_mut()?;
1659                waves.force_variable_name_type(name_type);
1660            }
1661            Message::CommandPromptClear => {
1662                *self.command_prompt_text.borrow_mut() = String::new();
1663                self.command_prompt.suggestions = vec![];
1664                // self.command_prompt.selected = self.command_prompt.previous_commands.len();
1665                self.command_prompt.selected = if self.command_prompt_text.borrow().is_empty() {
1666                    self.command_prompt.previous_commands.len().clamp(0, 3)
1667                } else {
1668                    0
1669                };
1670            }
1671            Message::CommandPromptUpdate { suggestions } => {
1672                self.command_prompt.suggestions = suggestions;
1673                self.command_prompt.selected = if self.command_prompt_text.borrow().is_empty() {
1674                    self.command_prompt.previous_commands.len().clamp(0, 3)
1675                } else {
1676                    0
1677                };
1678                self.command_prompt.new_selection =
1679                    Some(if self.command_prompt_text.borrow().is_empty() {
1680                        self.command_prompt.previous_commands.len().clamp(0, 3)
1681                    } else {
1682                        0
1683                    });
1684            }
1685            Message::CommandPromptPushPrevious(cmd) => {
1686                let len = cmd.len();
1687                self.command_prompt
1688                    .previous_commands
1689                    .insert(0, (cmd, vec![false; len]));
1690            }
1691            Message::OpenFileDialog(mode) => {
1692                self.open_file_dialog(mode);
1693            }
1694            Message::OpenCommandFileDialog => {
1695                self.open_command_file_dialog();
1696            }
1697            #[cfg(feature = "python")]
1698            Message::OpenPythonPluginDialog => {
1699                self.open_python_file_dialog();
1700            }
1701            #[cfg(feature = "python")]
1702            Message::ReloadPythonPlugin => {
1703                try_log_error!(
1704                    self.translators.reload_python_translator(),
1705                    "Error reloading Python translator"
1706                );
1707                self.invalidate_draw_commands();
1708            }
1709            Message::SaveStateFile(path) => self.save_state_file(path),
1710            Message::LoadStateFromData(bytes) => self.load_state_from_bytes(bytes),
1711            Message::LoadStateFile(path) => self.load_state_file(path),
1712            Message::LoadState(state, path) => self.load_state(state, path),
1713            Message::SetStateFile(path) => {
1714                // since in wasm we can't support "save", only "save as" - never set the `state_file`
1715                #[cfg(not(target_arch = "wasm32"))]
1716                {
1717                    self.user.state_file = Some(path);
1718                }
1719                #[cfg(target_arch = "wasm32")]
1720                {
1721                    error!("Failed to load {path:?}. Loading state files is unsupported on wasm")
1722                }
1723            }
1724            Message::SetAboutVisible(s) => self.user.show_about = s,
1725            Message::SetKeyHelpVisible(s) => self.user.show_keys = s,
1726            Message::SetGestureHelpVisible(s) => self.user.show_gestures = s,
1727            Message::SetUrlEntryVisible(s, f) => {
1728                self.user.show_url_entry = s;
1729                self.url_callback = f;
1730            }
1731            Message::SetLicenseVisible(s) => self.user.show_license = s,
1732            Message::SetQuickStartVisible(s) => self.user.show_quick_start = s,
1733            Message::SetPerformanceVisible(s) => {
1734                if !s {
1735                    self.continuous_redraw = false;
1736                }
1737                self.user.show_performance = s;
1738            }
1739            Message::SetContinuousRedraw(s) => self.continuous_redraw = s,
1740            Message::SetMouseGestureDragStart(pos) => self.gesture_start_location = pos,
1741            Message::SetMeasureDragStart(pos) => self.measure_start_location = pos,
1742            Message::SetFilterFocused(s) => self.user.variable_name_filter_focused = s,
1743            Message::SetVariableNameFilterType(variable_name_filter_type) => {
1744                self.user.variable_filter.name_filter_type = variable_name_filter_type;
1745            }
1746            Message::SetVariableNameFilterCaseInsensitive(s) => {
1747                self.user.variable_filter.name_filter_case_insensitive = s;
1748            }
1749            Message::SetVariableIOFilter(t, b) => match t {
1750                VariableIOFilterType::Output => self.user.variable_filter.include_outputs = b,
1751                VariableIOFilterType::Input => self.user.variable_filter.include_inputs = b,
1752                VariableIOFilterType::InOut => self.user.variable_filter.include_inouts = b,
1753                VariableIOFilterType::Other => self.user.variable_filter.include_others = b,
1754            },
1755            Message::SetVariableGroupByDirection(b) => {
1756                self.user.variable_filter.group_by_direction = b;
1757            }
1758            Message::SetUIZoomFactor(scale) => {
1759                if let Some(ctx) = &mut self.context.as_ref() {
1760                    ctx.set_zoom_factor(scale);
1761                }
1762                self.user.ui_zoom_factor = Some(scale);
1763            }
1764            Message::SelectPrevCommand => {
1765                self.command_prompt.new_selection = Some(
1766                    self.command_prompt
1767                        .new_selection
1768                        .unwrap_or(self.command_prompt.selected)
1769                        .saturating_sub(1)
1770                        .max(0),
1771                );
1772            }
1773            Message::SelectNextCommand => {
1774                self.command_prompt.new_selection = Some(
1775                    self.command_prompt
1776                        .new_selection
1777                        .unwrap_or(self.command_prompt.selected)
1778                        .saturating_add(1)
1779                        .min(self.command_prompt.suggestions.len().saturating_sub(1)),
1780                );
1781            }
1782            Message::SetHierarchyStyle(style) => self.user.hierarchy_style = Some(style),
1783            Message::SetArrowKeyBindings(bindings) => {
1784                self.user.arrow_key_bindings = Some(bindings);
1785            }
1786            Message::SetPrimaryMouseDragBehavior(behavior) => {
1787                self.user.primary_button_drag_behavior = Some(behavior);
1788            }
1789            Message::InvalidateDrawCommands => self.invalidate_draw_commands(),
1790            Message::UnpauseSimulation => {
1791                let waves = self.user.waves.as_ref()?;
1792                waves.inner.as_waves().unwrap().unpause_simulation();
1793            }
1794            Message::PauseSimulation => {
1795                let waves = self.user.waves.as_ref()?;
1796                waves.inner.as_waves().unwrap().pause_simulation();
1797            }
1798            Message::Batch(messages) => {
1799                for message in messages {
1800                    self.update(message);
1801                }
1802            }
1803            Message::AddDraggedVariables(variables) => {
1804                let waves = self.user.waves.as_mut()?;
1805
1806                waves.focused_item = None;
1807                self.user.drag_source_idx = None;
1808                let target = self.user.drag_target_idx.take();
1809
1810                if let (Some(cmd), _) =
1811                    waves.add_variables(&self.translators, variables, target, true, false, None)
1812                {
1813                    self.load_variables(cmd);
1814                }
1815                self.invalidate_draw_commands();
1816            }
1817            Message::VariableDragStarted(vidx) => {
1818                self.user.drag_started = true;
1819                self.user.drag_source_idx = Some(vidx);
1820                self.user.drag_target_idx = None;
1821            }
1822            Message::VariableDragTargetChanged(position) => {
1823                self.user.drag_target_idx = Some(position);
1824            }
1825            Message::VariableDragFinished => {
1826                self.user.drag_started = false;
1827
1828                let source_vidx = self.user.drag_source_idx.take()?;
1829                let target_position = self.user.drag_target_idx.take()?;
1830
1831                // reordering
1832                self.save_current_canvas("Drag item".to_string());
1833                self.invalidate_draw_commands();
1834                let waves = self.user.waves.as_mut()?;
1835
1836                let focused_index = waves
1837                    .focused_item
1838                    .and_then(|vidx| waves.items_tree.to_displayed(vidx));
1839                let focused_item_ref = focused_index
1840                    .and_then(|idx| waves.items_tree.get(idx))
1841                    .map(|node| node.item_ref);
1842
1843                let mut to_move = waves
1844                    .items_tree
1845                    .iter_visible_extra()
1846                    .filter_map(|info| info.node.selected.then_some(info.idx))
1847                    .collect::<Vec<_>>();
1848                if let Some(idx) = focused_index {
1849                    to_move.push(idx);
1850                }
1851                if let Some(vidx) = waves.items_tree.to_displayed(source_vidx) {
1852                    to_move.push(vidx);
1853                }
1854
1855                let _ = waves.items_tree.move_items(to_move, target_position);
1856
1857                waves.focused_item = focused_item_ref
1858                    .and_then(|item_ref| {
1859                        waves
1860                            .items_tree
1861                            .iter_visible()
1862                            .position(|node| node.item_ref == item_ref)
1863                    })
1864                    .map(VisibleItemIndex);
1865            }
1866            Message::VariableValueToClipbord(vidx) => {
1867                self.handle_variable_clipboard_operation(
1868                    vidx,
1869                    |waves, item_ref: DisplayedItemRef| {
1870                        if let Some(DisplayedItem::Variable(_)) =
1871                            waves.displayed_items.get(&item_ref)
1872                        {
1873                            let field_ref = item_ref.into();
1874                            self.get_variable_value(
1875                                waves,
1876                                &field_ref,
1877                                waves
1878                                    .cursor
1879                                    .as_ref()
1880                                    .and_then(num::BigInt::to_biguint)
1881                                    .as_ref(),
1882                            )
1883                        } else {
1884                            None
1885                        }
1886                    },
1887                );
1888            }
1889            Message::VariableNameToClipboard(vidx) => {
1890                self.handle_variable_clipboard_operation(
1891                    vidx,
1892                    |waves, item_ref: DisplayedItemRef| {
1893                        if let Some(DisplayedItem::Variable(variable)) =
1894                            waves.displayed_items.get(&item_ref)
1895                        {
1896                            Some(variable.variable_ref.name.clone())
1897                        } else {
1898                            None
1899                        }
1900                    },
1901                );
1902            }
1903            Message::VariableFullNameToClipboard(vidx) => {
1904                self.handle_variable_clipboard_operation(
1905                    vidx,
1906                    |waves, item_ref: DisplayedItemRef| {
1907                        if let Some(DisplayedItem::Variable(variable)) =
1908                            waves.displayed_items.get(&item_ref)
1909                        {
1910                            Some(variable.variable_ref.full_path_string())
1911                        } else {
1912                            None
1913                        }
1914                    },
1915                );
1916            }
1917            Message::SetViewportStrategy(s) => {
1918                if let Some(waves) = &mut self.user.waves {
1919                    for vp in &mut waves.viewports {
1920                        vp.move_strategy = s;
1921                    }
1922                }
1923            }
1924            Message::Undo(count) => {
1925                let waves = self.user.waves.as_mut()?;
1926                for _ in 0..count {
1927                    if let Some(prev_state) = self.undo_stack.pop() {
1928                        self.redo_stack
1929                            .push(SystemState::current_canvas_state(waves, prev_state.message));
1930                        waves.focused_item = prev_state.focused_item;
1931                        waves.focused_transaction = prev_state.focused_transaction;
1932                        waves.items_tree = prev_state.items_tree;
1933                        waves.displayed_items = prev_state.displayed_items;
1934                        waves.markers = prev_state.markers;
1935                    } else {
1936                        break;
1937                    }
1938                }
1939                self.invalidate_draw_commands();
1940            }
1941            Message::Redo(count) => {
1942                let waves = self.user.waves.as_mut()?;
1943                for _ in 0..count {
1944                    if let Some(prev_state) = self.redo_stack.pop() {
1945                        self.undo_stack
1946                            .push(SystemState::current_canvas_state(waves, prev_state.message));
1947                        waves.focused_item = prev_state.focused_item;
1948                        waves.focused_transaction = prev_state.focused_transaction;
1949                        waves.items_tree = prev_state.items_tree;
1950                        waves.displayed_items = prev_state.displayed_items;
1951                        waves.markers = prev_state.markers;
1952                    } else {
1953                        break;
1954                    }
1955                }
1956                self.invalidate_draw_commands();
1957            }
1958            Message::DumpTree => {
1959                let waves = self.user.waves.as_ref()?;
1960                dump_tree(waves);
1961            }
1962            Message::GroupNew {
1963                name,
1964                before,
1965                items,
1966            } => {
1967                self.save_current_canvas(format!(
1968                    "Create group {}",
1969                    name.clone().unwrap_or(String::new())
1970                ));
1971                self.invalidate_draw_commands();
1972                let waves = self.user.waves.as_mut()?;
1973
1974                let passed_or_focused = before
1975                    .and_then(|before| {
1976                        waves
1977                            .items_tree
1978                            .get(before)
1979                            .map(|node| node.level)
1980                            .map(|level| TargetPosition { before, level })
1981                    })
1982                    .or_else(|| waves.insert_position(waves.focused_item));
1983                let final_target = passed_or_focused.unwrap_or_else(|| waves.end_insert_position());
1984
1985                let mut item_refs = items.unwrap_or_else(|| {
1986                    waves
1987                        .items_tree
1988                        .iter_visible_selected()
1989                        .map(|node| node.item_ref)
1990                        .collect::<Vec<_>>()
1991                });
1992
1993                // if we are using the focus as the insert anchor, then move that as well
1994                let item_refs = if before.is_none() & passed_or_focused.is_some() {
1995                    let focus_index = waves
1996                        .items_tree
1997                        .to_displayed(waves.focused_item.expect("Inconsistent state"))
1998                        .expect("Inconsistent state");
1999                    item_refs.push(
2000                        waves
2001                            .items_tree
2002                            .get(focus_index)
2003                            .expect("Inconsistent state")
2004                            .item_ref,
2005                    );
2006                    item_refs
2007                } else {
2008                    item_refs
2009                };
2010
2011                if item_refs.is_empty() {
2012                    return None;
2013                }
2014
2015                let group_ref =
2016                    waves.add_group(name.unwrap_or("Group".to_owned()), Some(final_target));
2017
2018                let item_idxs = waves
2019                    .items_tree
2020                    .iter()
2021                    .enumerate()
2022                    .filter_map(|(idx, node)| {
2023                        item_refs
2024                            .contains(&node.item_ref)
2025                            .then_some(crate::displayed_item_tree::ItemIndex(idx))
2026                    })
2027                    .collect::<Vec<_>>();
2028
2029                if let Err(e) = waves.items_tree.move_items(
2030                    item_idxs,
2031                    crate::displayed_item_tree::TargetPosition {
2032                        before: ItemIndex(final_target.before.0 + 1),
2033                        level: final_target.level.saturating_add(1),
2034                    },
2035                ) {
2036                    dump_tree(waves);
2037                    error!("failed to move items into group: {e:?}");
2038                }
2039                waves.items_tree.xselect_all_visible(false);
2040                waves.focused_item = waves
2041                    .items_tree
2042                    .iter_visible_extra()
2043                    .find_map(|info| (info.node.item_ref == group_ref).then_some(info.vidx));
2044            }
2045            Message::GroupDissolve(item_ref) => {
2046                self.save_current_canvas("Dissolve group".to_owned());
2047                self.invalidate_draw_commands();
2048                let waves = self.user.waves.as_mut()?;
2049                let item_index = waves.index_for_ref_or_focus(item_ref)?;
2050
2051                waves.items_tree.remove_dissolve(item_index);
2052            }
2053            Message::GroupFold(item_ref)
2054            | Message::GroupUnfold(item_ref)
2055            | Message::GroupFoldRecursive(item_ref)
2056            | Message::GroupUnfoldRecursive(item_ref) => {
2057                let unfold = matches!(
2058                    message,
2059                    Message::GroupUnfold(..) | Message::GroupUnfoldRecursive(..)
2060                );
2061                let recursive = matches!(
2062                    message,
2063                    Message::GroupFoldRecursive(..) | Message::GroupUnfoldRecursive(..)
2064                );
2065
2066                let undo_msg = if unfold {
2067                    "Unfold group".to_owned()
2068                } else {
2069                    "Fold group".to_owned()
2070                } + &(if recursive {
2071                    " recursive".to_owned()
2072                } else {
2073                    String::new()
2074                });
2075                // TODO add group name? would have to break the pattern that we insert an
2076                // undo message even if no waves are available
2077                self.save_current_canvas(undo_msg);
2078                self.invalidate_draw_commands();
2079
2080                let waves = self.user.waves.as_mut()?;
2081                let item = waves.index_for_ref_or_focus(item_ref)?;
2082
2083                if let Some(focused_item) = waves.focused_item {
2084                    let info = waves
2085                        .items_tree
2086                        .get_visible_extra(focused_item)
2087                        .expect("Inconsistent state");
2088                    if waves.items_tree.subtree_contains(item, info.idx) {
2089                        waves.focused_item = None;
2090                    }
2091                }
2092                if recursive {
2093                    waves.items_tree.xfold_recursive(item, unfold);
2094                } else {
2095                    waves.items_tree.xfold(item, unfold);
2096                }
2097            }
2098            Message::GroupFoldAll | Message::GroupUnfoldAll => {
2099                let unfold = matches!(message, Message::GroupUnfoldAll);
2100                let undo_msg = if unfold {
2101                    "Fold all groups".to_owned()
2102                } else {
2103                    "Unfold all groups".to_owned()
2104                };
2105                self.save_current_canvas(undo_msg);
2106                self.invalidate_draw_commands();
2107
2108                let waves = self.user.waves.as_mut()?;
2109
2110                // remove focus if focused item is folded away -> prevent future waveform
2111                // adds being invisibly inserted
2112                if let Some(focused_item) = waves.focused_item {
2113                    let focused_level = waves
2114                        .items_tree
2115                        .get_visible(focused_item)
2116                        .expect("Inconsistent state")
2117                        .level;
2118                    if !unfold & (focused_level > 0) {
2119                        waves.focused_item = None;
2120                    }
2121                }
2122                waves.items_tree.xfold_all(unfold);
2123            }
2124            #[cfg(target_arch = "wasm32")]
2125            Message::StartWcpServer { .. } => {
2126                error!("Wcp is not supported on wasm")
2127            }
2128            #[cfg(target_arch = "wasm32")]
2129            Message::StopWcpServer => {
2130                error!("Wcp is not supported on wasm")
2131            }
2132            #[cfg(not(target_arch = "wasm32"))]
2133            Message::StartWcpServer { address, initiate } => {
2134                self.start_wcp_server(address, initiate);
2135            }
2136            #[cfg(not(target_arch = "wasm32"))]
2137            Message::StopWcpServer => {
2138                self.stop_wcp_server();
2139            }
2140            Message::SetupChannelWCP => {
2141                #[cfg(target_arch = "wasm32")]
2142                {
2143                    use futures::executor::block_on;
2144                    self.channels.wcp_c2s_receiver = block_on(WCP_CS_HANDLER.rx.write()).take();
2145                    if self.channels.wcp_c2s_receiver.is_none() {
2146                        error!("Failed to claim wasm tx, was SetupWasmWCP executed twice?");
2147                    }
2148                    self.channels.wcp_s2c_sender = Some(WCP_SC_HANDLER.tx.clone());
2149                }
2150            }
2151            Message::BuildAnalogCache {
2152                display_id,
2153                cache_key,
2154            } => {
2155                let waves = self.user.waves.as_mut()?;
2156                let generation = waves.cache_generation;
2157
2158                // Check if already have valid entry (building or ready)
2159                let item = waves.displayed_items.get(&display_id)?;
2160                let DisplayedItem::Variable(var) = item else {
2161                    return None;
2162                };
2163                if var
2164                    .analog
2165                    .as_ref()?
2166                    .cache
2167                    .as_ref()
2168                    .is_some_and(|e| e.generation == generation && e.cache_key == cache_key)
2169                {
2170                    return None;
2171                }
2172
2173                // Try to share from in-flight builds first (handles removed-but-still-building case)
2174                if let Some(entry) = waves.inflight_caches.get(&cache_key)
2175                    && entry.generation == generation
2176                {
2177                    if let DisplayedItem::Variable(var) =
2178                        waves.displayed_items.get_mut(&display_id)?
2179                    {
2180                        var.analog.as_mut()?.cache = Some(entry.clone());
2181                    }
2182                    return None; // Shared from in-flight build
2183                }
2184
2185                // Try to share from another displayed variable (O(n) scan - only during cache build)
2186                let existing = waves
2187                    .displayed_items
2188                    .values()
2189                    .filter_map(|item| match item {
2190                        DisplayedItem::Variable(v) => v.analog.as_ref()?.cache.as_ref(),
2191                        _ => None,
2192                    })
2193                    .find(|e| e.cache_key == cache_key && e.generation == generation)
2194                    .cloned();
2195
2196                if let Some(entry) = existing {
2197                    if let DisplayedItem::Variable(var) =
2198                        waves.displayed_items.get_mut(&display_id)?
2199                    {
2200                        var.analog.as_mut()?.cache = Some(entry);
2201                    }
2202                    return None; // Shared existing entry (may still be building)
2203                }
2204
2205                // Clone variable_ref only when we need to spawn builder
2206                let variable_ref = match waves.displayed_items.get(&display_id)? {
2207                    DisplayedItem::Variable(v) => v.variable_ref.clone(),
2208                    _ => return None,
2209                };
2210
2211                // Create new entry and spawn builder
2212                let entry = std::sync::Arc::new(crate::analog_signal_cache::AnalogCacheEntry::new(
2213                    cache_key.clone(),
2214                    generation,
2215                ));
2216
2217                if let DisplayedItem::Variable(var) = waves.displayed_items.get_mut(&display_id)? {
2218                    var.analog.as_mut()?.cache = Some(entry.clone());
2219                }
2220
2221                let translator = self.translators.clone_translator(&cache_key.1);
2222
2223                // Track in-flight build for sharing with other variables
2224                waves
2225                    .inflight_caches
2226                    .insert(cache_key.clone(), entry.clone());
2227
2228                waves.build_analog_cache_async(
2229                    entry,
2230                    &variable_ref,
2231                    translator,
2232                    &self.channels.msg_sender,
2233                );
2234            }
2235            Message::AnalogCacheBuilt { entry, result } => {
2236                OUTSTANDING_TRANSACTIONS.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
2237                // Remove from in-flight registry (may already be gone if generation changed)
2238                if let Some(waves) = self.user.waves.as_mut() {
2239                    waves.inflight_caches.remove(&entry.cache_key);
2240                }
2241                match result {
2242                    Ok(cache) => {
2243                        entry.set(cache);
2244                    }
2245                    Err(err) => {
2246                        warn!("Failed to build analog cache: {err}");
2247                    }
2248                }
2249                self.invalidate_draw_commands();
2250            }
2251            Message::Exit | Message::ToggleFullscreen => {} // Handled in eframe::update
2252            Message::AddViewport => {
2253                let waves = self.user.waves.as_mut()?;
2254                let viewport = Viewport::new();
2255                waves.viewports.push(viewport);
2256                self.draw_data.borrow_mut().push(None);
2257            }
2258            Message::RemoveViewport => {
2259                let waves = self.user.waves.as_mut()?;
2260                if waves.viewports.len() > 1 {
2261                    waves.viewports.pop();
2262                    self.draw_data.borrow_mut().pop();
2263                }
2264            }
2265            Message::SelectTheme(theme_name) => {
2266                let theme = SurferTheme::new(theme_name)
2267                    .with_context(|| "Failed to set theme")
2268                    .ok()?;
2269                self.user.config.theme = theme;
2270                let ctx = self.context.as_ref()?;
2271                ctx.set_visuals(self.get_visuals());
2272            }
2273            Message::EnableAnimations(enable) => {
2274                let ctx = self.context.as_ref()?;
2275                self.user.animation_enabled = Some(enable);
2276                ctx.all_styles_mut(|style| {
2277                    style.animation_time = if self.animation_enabled() {
2278                        self.user.config.animation_time
2279                    } else {
2280                        0.0
2281                    };
2282                });
2283            }
2284            Message::AsyncDone(_) => (),
2285            Message::AddGraphic(id, g) => {
2286                let waves = self.user.waves.as_mut()?;
2287                waves.graphics.insert(id, g);
2288            }
2289            Message::RemoveGraphic(id) => {
2290                let waves = self.user.waves.as_mut()?;
2291                waves.graphics.retain(|k, _| k != &id);
2292            }
2293            Message::ExpandDrawnItem { item, levels } => {
2294                self.items_to_expand.borrow_mut().push((item, levels));
2295            }
2296            Message::AddCharToPrompt(c) => *self.char_to_add_to_prompt.borrow_mut() = Some(c),
2297        }
2298        Some(())
2299    }
2300
2301    pub fn add_scope_as_group(
2302        &mut self,
2303        scope: &ScopeRef,
2304        pos: TargetPosition,
2305        recursive: bool,
2306        variable_name_type: Option<VariableNameType>,
2307    ) -> TargetPosition {
2308        let Some(waves) = self.user.waves.as_mut() else {
2309            return pos;
2310        };
2311        let Some(container) = waves.inner.as_waves() else {
2312            return pos;
2313        };
2314
2315        let variables = container
2316            .variables_in_scope(scope)
2317            .iter()
2318            .sorted_by(|a, b| numeric_sort::cmp(&a.name, &b.name))
2319            .cloned()
2320            .collect_vec();
2321        let child_scopes = container.child_scopes(scope);
2322        let variable_name_type = variable_name_type.or_else(|| {
2323            container
2324                .scope_is_variable(scope)
2325                .then_some(VariableNameType::Local)
2326        });
2327
2328        waves.add_group(scope.name(), Some(pos));
2329        let into_group_pos = TargetPosition {
2330            before: ItemIndex(pos.before.0 + 1),
2331            level: pos.level + 1,
2332        };
2333
2334        let (cmd, variable_refs) = waves.add_variables(
2335            &self.translators,
2336            variables,
2337            Some(into_group_pos),
2338            false,
2339            false,
2340            variable_name_type,
2341        );
2342        let mut into_group_pos = TargetPosition {
2343            before: ItemIndex(into_group_pos.before.0 + variable_refs.len()),
2344            level: pos.level + 1,
2345        };
2346
2347        if let Some(cmd) = cmd {
2348            self.load_variables(cmd);
2349        }
2350
2351        if recursive {
2352            for child in child_scopes.unwrap_or(vec![]) {
2353                into_group_pos =
2354                    self.add_scope_as_group(&child, into_group_pos, recursive, variable_name_type);
2355                into_group_pos.level = pos.level + 1;
2356            }
2357        }
2358        into_group_pos
2359    }
2360
2361    fn handle_variable_clipboard_operation<F>(
2362        &self,
2363        vidx: MessageTarget<VisibleItemIndex>,
2364        get_text: F,
2365    ) where
2366        F: FnOnce(&WaveData, DisplayedItemRef) -> Option<String>,
2367    {
2368        let Some(waves) = &self.user.waves else {
2369            return;
2370        };
2371        let vidx = if let MessageTarget::Explicit(vidx) = vidx {
2372            vidx
2373        } else if let Some(focused) = waves.focused_item {
2374            focused
2375        } else {
2376            return;
2377        };
2378        let Some(item_ref) = waves.items_tree.get_visible(vidx).map(|node| node.item_ref) else {
2379            return;
2380        };
2381
2382        if let Some(text) = get_text(waves, item_ref)
2383            && let Some(ctx) = &self.context
2384        {
2385            ctx.copy_text(text);
2386        }
2387    }
2388}
2389
2390pub fn dump_tree(waves: &WaveData) {
2391    let mut result = String::new();
2392    for (idx, node) in waves.items_tree.iter().enumerate() {
2393        for _ in 0..node.level.saturating_sub(1) {
2394            result.push(' ');
2395        }
2396
2397        if node.level > 0 {
2398            match waves.items_tree.get(ItemIndex(idx + 1)) {
2399                Some(next) if next.level < node.level => result.push_str("╰╴"),
2400                _ => result.push_str("├╴"),
2401            }
2402        }
2403
2404        result.push_str(
2405            &waves
2406                .displayed_items
2407                .get(&node.item_ref)
2408                .map_or("?".to_owned(), displayed_item::DisplayedItem::name),
2409        );
2410        result.push_str(&format!("   ({:?})", node.item_ref));
2411        if node.selected {
2412            result.push_str(" !SEL! ");
2413        }
2414        result.push('\n');
2415    }
2416    info!("tree: \n{}", &result);
2417}
2418
2419pub struct StateWrapper(Arc<RwLock<SystemState>>);
2420impl App for StateWrapper {
2421    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
2422        App::update(&mut *self.0.write().unwrap(), ctx, frame);
2423    }
2424}