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