envision 0.15.1

A ratatui framework for collaborative TUI development with headless testing support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
//! Annotation types for widget metadata.

use std::collections::HashMap;

/// The type of widget being annotated.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(
    feature = "serialization",
    derive(serde::Serialize, serde::Deserialize)
)]
#[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))]
pub enum WidgetType {
    /// A container or panel
    Container,

    /// A dialog or modal
    Dialog,

    /// A button
    Button,

    /// A text input field
    Input,

    /// A text area (multi-line input)
    TextArea,

    /// A checkbox
    Checkbox,

    /// A radio button
    Radio,

    /// A dropdown/select
    Select,

    /// A list widget
    List,

    /// A table widget
    Table,

    /// A tab bar
    TabBar,

    /// A single tab
    Tab,

    /// A menu
    Menu,

    /// A menu item
    MenuItem,

    /// A label/text display
    Label,

    /// A header/title
    Header,

    /// A footer
    Footer,

    /// A sidebar
    Sidebar,

    /// A toolbar
    Toolbar,

    /// A status bar
    StatusBar,

    /// A progress indicator
    Progress,

    /// A scrollable area
    Scroll,

    /// A tree view
    Tree,

    /// A spinner/loading indicator
    Spinner,

    /// A toast notification
    Toast,

    /// A tooltip overlay
    Tooltip,

    /// An accordion panel group
    Accordion,

    /// A breadcrumb navigation trail
    Breadcrumb,

    /// A loading list
    LoadingList,

    /// A key hints display
    KeyHints,

    /// A multi-progress display
    MultiProgress,

    /// A status log
    StatusLog,

    /// A title card display
    TitleCard,

    /// A line input
    LineInput,

    /// A dropdown select
    Dropdown,

    /// A scrollable text display
    ScrollableText,

    /// A form container
    Form,

    /// A split panel container
    SplitPanel,

    /// A searchable list container
    SearchableList,

    /// A radio group
    RadioGroup,

    /// A file browser
    FileBrowser,

    /// A confirm dialog
    ConfirmDialog,

    /// A step indicator
    StepIndicator,

    /// A styled text display
    StyledText,

    /// A pane layout manager
    PaneLayout,

    /// A sparkline data trend display
    Sparkline,

    /// A divider/separator line
    Divider,

    /// A canvas drawing surface
    Canvas,

    /// A paginator navigation indicator
    Paginator,

    /// A help panel display
    HelpPanel,

    /// A toggle switch
    Switch,

    /// A command palette overlay
    CommandPalette,

    /// A big text display
    BigText,

    /// A span tree / trace timeline
    SpanTree,

    /// A diff viewer
    DiffViewer,

    /// A terminal output display
    TerminalOutput,

    /// A custom widget type
    Custom(String),
}

impl WidgetType {
    /// Returns true if this is an interactive widget (can receive input).
    pub fn is_interactive(&self) -> bool {
        matches!(
            self,
            WidgetType::Button
                | WidgetType::Input
                | WidgetType::TextArea
                | WidgetType::Checkbox
                | WidgetType::Radio
                | WidgetType::Select
                | WidgetType::List
                | WidgetType::Table
                | WidgetType::Tab
                | WidgetType::MenuItem
                | WidgetType::Tree
                | WidgetType::LineInput
                | WidgetType::Dropdown
                | WidgetType::LoadingList
                | WidgetType::Accordion
                | WidgetType::RadioGroup
                | WidgetType::SearchableList
                | WidgetType::FileBrowser
                | WidgetType::StepIndicator
                | WidgetType::Paginator
                | WidgetType::Switch
                | WidgetType::CommandPalette
                | WidgetType::SpanTree
        )
    }

    /// Returns true if this is a container widget.
    pub fn is_container(&self) -> bool {
        matches!(
            self,
            WidgetType::Container
                | WidgetType::Dialog
                | WidgetType::ConfirmDialog
                | WidgetType::Scroll
                | WidgetType::Sidebar
                | WidgetType::Form
                | WidgetType::SplitPanel
        )
    }
}

impl std::fmt::Display for WidgetType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            WidgetType::Custom(name) => write!(f, "{}", name),
            other => write!(f, "{:?}", other),
        }
    }
}

/// Metadata annotation for a widget.
///
/// Annotations provide semantic information about widgets that
/// can be used for testing, accessibility, and UI queries.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
    feature = "serialization",
    derive(serde::Serialize, serde::Deserialize)
)]
pub struct Annotation {
    /// The type of widget
    pub widget_type: WidgetType,

    /// Human-readable label
    pub label: Option<String>,

    /// Programmatic identifier
    pub id: Option<String>,

    /// Whether this widget currently has focus
    pub focused: bool,

    /// Whether this widget is disabled
    pub disabled: bool,

    /// Whether this widget is selected (for selectable items)
    pub selected: bool,

    /// Whether this widget is expanded (for collapsible items)
    pub expanded: Option<bool>,

    /// Current value (for inputs, etc.)
    pub value: Option<String>,

    /// Additional metadata
    #[cfg_attr(
        feature = "serialization",
        serde(default, skip_serializing_if = "HashMap::is_empty")
    )]
    pub metadata: HashMap<String, String>,
}

impl Annotation {
    /// Creates a new annotation with the given widget type.
    pub fn new(widget_type: WidgetType) -> Self {
        Self {
            widget_type,
            label: None,
            id: None,
            focused: false,
            disabled: false,
            selected: false,
            expanded: None,
            value: None,
            metadata: HashMap::new(),
        }
    }

    // Convenience constructors for common widget types

    /// Creates a container annotation.
    pub fn container(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Container).with_id(id)
    }

    /// Creates a dialog annotation.
    pub fn dialog(title: impl Into<String>) -> Self {
        Self::new(WidgetType::Dialog).with_label(title)
    }

    /// Creates a button annotation.
    pub fn button(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Button).with_id(id)
    }

    /// Creates an input field annotation.
    pub fn input(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Input).with_id(id)
    }

    /// Creates a text area annotation.
    pub fn text_area(id: impl Into<String>) -> Self {
        Self::new(WidgetType::TextArea).with_id(id)
    }

    /// Creates a checkbox annotation.
    pub fn checkbox(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Checkbox).with_id(id)
    }

    /// Creates a list annotation.
    pub fn list(id: impl Into<String>) -> Self {
        Self::new(WidgetType::List).with_id(id)
    }

    /// Creates a table annotation.
    pub fn table(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Table).with_id(id)
    }

    /// Creates a tab annotation.
    pub fn tab(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Tab).with_id(id)
    }

    /// Creates a menu item annotation.
    pub fn menu_item(id: impl Into<String>) -> Self {
        Self::new(WidgetType::MenuItem).with_id(id)
    }

    /// Creates a label annotation.
    pub fn label(text: impl Into<String>) -> Self {
        Self::new(WidgetType::Label).with_label(text)
    }

    /// Creates a header annotation.
    pub fn header(text: impl Into<String>) -> Self {
        Self::new(WidgetType::Header).with_label(text)
    }

    /// Creates a spinner annotation.
    pub fn spinner(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Spinner).with_id(id)
    }

    /// Creates a toast annotation.
    pub fn toast(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Toast).with_id(id)
    }

    /// Creates a tooltip annotation.
    pub fn tooltip(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Tooltip).with_id(id)
    }

    /// Creates an accordion annotation.
    pub fn accordion(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Accordion).with_id(id)
    }

    /// Creates a breadcrumb annotation.
    pub fn breadcrumb(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Breadcrumb).with_id(id)
    }

    /// Creates a loading list annotation.
    pub fn loading_list(id: impl Into<String>) -> Self {
        Self::new(WidgetType::LoadingList).with_id(id)
    }

    /// Creates a key hints annotation.
    pub fn key_hints(id: impl Into<String>) -> Self {
        Self::new(WidgetType::KeyHints).with_id(id)
    }

    /// Creates a multi-progress annotation.
    pub fn multi_progress(id: impl Into<String>) -> Self {
        Self::new(WidgetType::MultiProgress).with_id(id)
    }

    /// Creates a status log annotation.
    pub fn status_log(id: impl Into<String>) -> Self {
        Self::new(WidgetType::StatusLog).with_id(id)
    }

    /// Creates a title card annotation.
    pub fn title_card(id: impl Into<String>) -> Self {
        Self::new(WidgetType::TitleCard).with_id(id)
    }

    /// Creates a line input annotation.
    pub fn line_input(id: impl Into<String>) -> Self {
        Self::new(WidgetType::LineInput).with_id(id)
    }

    /// Creates a dropdown annotation.
    pub fn dropdown(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Dropdown).with_id(id)
    }

    /// Creates a scrollable text annotation.
    pub fn scrollable_text(id: impl Into<String>) -> Self {
        Self::new(WidgetType::ScrollableText).with_id(id)
    }

    /// Creates a form annotation.
    pub fn form(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Form).with_id(id)
    }

    /// Creates a split panel annotation.
    pub fn split_panel(id: impl Into<String>) -> Self {
        Self::new(WidgetType::SplitPanel).with_id(id)
    }

    /// Creates a searchable list annotation.
    pub fn searchable_list(id: impl Into<String>) -> Self {
        Self::new(WidgetType::SearchableList).with_id(id)
    }

    /// Creates a radio group annotation.
    pub fn radio_group(id: impl Into<String>) -> Self {
        Self::new(WidgetType::RadioGroup).with_id(id)
    }

    /// Creates a file browser annotation.
    pub fn file_browser(id: impl Into<String>) -> Self {
        Self::new(WidgetType::FileBrowser).with_id(id)
    }

    /// Creates a confirm dialog annotation.
    pub fn confirm_dialog(title: impl Into<String>) -> Self {
        Self::new(WidgetType::ConfirmDialog).with_label(title)
    }

    /// Creates a step indicator annotation.
    pub fn step_indicator(id: impl Into<String>) -> Self {
        Self::new(WidgetType::StepIndicator).with_id(id)
    }

    /// Creates a styled text annotation.
    pub fn styled_text(id: impl Into<String>) -> Self {
        Self::new(WidgetType::StyledText).with_id(id)
    }

    /// Creates a pane layout annotation.
    pub fn pane_layout(id: impl Into<String>) -> Self {
        Self::new(WidgetType::PaneLayout).with_id(id)
    }

    /// Creates a sparkline annotation.
    pub fn sparkline(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Sparkline).with_id(id)
    }

    /// Creates a divider annotation.
    pub fn divider(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Divider).with_id(id)
    }

    /// Creates a canvas annotation.
    pub fn canvas(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Canvas).with_id(id)
    }

    /// Creates a paginator annotation.
    pub fn paginator(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Paginator).with_id(id)
    }

    /// Creates a help panel annotation.
    pub fn help_panel(id: impl Into<String>) -> Self {
        Self::new(WidgetType::HelpPanel).with_id(id)
    }

    /// Creates a switch annotation.
    pub fn switch(id: impl Into<String>) -> Self {
        Self::new(WidgetType::Switch).with_id(id)
    }

    /// Creates a command palette annotation.
    pub fn command_palette(id: impl Into<String>) -> Self {
        Self::new(WidgetType::CommandPalette).with_id(id)
    }

    /// Creates a big text annotation.
    pub fn big_text(id: impl Into<String>) -> Self {
        Self::new(WidgetType::BigText).with_id(id)
    }

    /// Creates a span tree annotation.
    pub fn span_tree(id: impl Into<String>) -> Self {
        Self::new(WidgetType::SpanTree).with_id(id)
    }

    /// Creates a diff viewer annotation.
    pub fn diff_viewer(id: impl Into<String>) -> Self {
        Self::new(WidgetType::DiffViewer).with_id(id)
    }

    /// Creates a terminal output annotation.
    pub fn terminal_output(id: impl Into<String>) -> Self {
        Self::new(WidgetType::TerminalOutput).with_id(id)
    }

    /// Creates a custom widget annotation.
    pub fn custom(type_name: impl Into<String>, id: impl Into<String>) -> Self {
        Self::new(WidgetType::Custom(type_name.into())).with_id(id)
    }

    // Builder methods

    /// Sets the label.
    pub fn with_label(mut self, label: impl Into<String>) -> Self {
        self.label = Some(label.into());
        self
    }

    /// Sets the id.
    pub fn with_id(mut self, id: impl Into<String>) -> Self {
        self.id = Some(id.into());
        self
    }

    /// Sets the focused state.
    pub fn with_focus(mut self, focused: bool) -> Self {
        self.focused = focused;
        self
    }

    /// Sets the disabled state.
    pub fn with_disabled(mut self, disabled: bool) -> Self {
        self.disabled = disabled;
        self
    }

    /// Sets the selected state.
    pub fn with_selected(mut self, selected: bool) -> Self {
        self.selected = selected;
        self
    }

    /// Sets the expanded state.
    pub fn with_expanded(mut self, expanded: bool) -> Self {
        self.expanded = Some(expanded);
        self
    }

    /// Sets the current value.
    pub fn with_value(mut self, value: impl Into<String>) -> Self {
        self.value = Some(value.into());
        self
    }

    /// Adds metadata.
    pub fn with_meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.metadata.insert(key.into(), value.into());
        self
    }

    // Query methods

    /// Returns true if this annotation matches the given id.
    pub fn has_id(&self, id: &str) -> bool {
        self.id.as_deref() == Some(id)
    }

    /// Returns true if this annotation has the given widget type.
    pub fn is_type(&self, widget_type: &WidgetType) -> bool {
        &self.widget_type == widget_type
    }

    /// Returns true if this is an interactive widget.
    pub fn is_interactive(&self) -> bool {
        self.widget_type.is_interactive() && !self.disabled
    }

    /// Returns a description suitable for display.
    pub fn description(&self) -> String {
        let mut parts = Vec::new();

        parts.push(format!("{:?}", self.widget_type));

        if let Some(label) = &self.label {
            parts.push(format!("\"{}\"", label));
        }

        if let Some(id) = &self.id {
            parts.push(format!("#{}", id));
        }

        if self.focused {
            parts.push("(focused)".to_string());
        }

        if self.disabled {
            parts.push("(disabled)".to_string());
        }

        if self.selected {
            parts.push("(selected)".to_string());
        }

        parts.join(" ")
    }
}

impl Default for Annotation {
    fn default() -> Self {
        Self::new(WidgetType::Container)
    }
}

#[cfg(test)]
mod tests;