libsurfer/
lib.rs

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