cli_text_reader/
demo_components.rs

1use crate::demo_script::DemoAction;
2use crossterm::event::{KeyCode, KeyModifiers};
3use std::time::Duration;
4
5#[derive(Clone, Debug)]
6pub struct DemoComponent {
7  pub id: &'static str,          // Unique identifier
8  pub name: &'static str,        // Human-readable name
9  pub description: &'static str, // Brief description
10  pub actions: Vec<DemoAction>,  // Sequence of actions
11}
12
13// Get a component by its ID
14pub fn get_component(id: &str) -> Option<DemoComponent> {
15  match id {
16    // Navigation Components
17    "basic_navigation" => Some(basic_navigation_component()),
18    "word_navigation" => Some(word_navigation_component()),
19    "paragraph_navigation" => Some(paragraph_navigation_component()),
20    "search_navigation" => Some(search_navigation_component()),
21
22    // Selection Components
23    "select_word" => Some(select_word_component()),
24    "select_paragraph" => Some(select_paragraph_component()),
25    "select_line" => Some(select_line_component()),
26    "visual_char_mode" => Some(visual_char_mode_component()),
27
28    // Action Components
29    "yank_line" => Some(yank_line_component()),
30    "yank_selection" => Some(yank_selection_component()),
31    "highlight_selection" => Some(highlight_selection_component()),
32    "clear_highlights" => Some(clear_highlights_component()),
33
34    // Command Components
35    "execute_ls" => Some(execute_ls_component()),
36    "execute_cat" => Some(execute_cat_component()),
37    "execute_grep" => Some(execute_grep_component()),
38    "yank_and_execute" => Some(yank_and_execute_component()),
39    "execute_cat_with_paste" => Some(execute_cat_with_paste_component()),
40
41    // Additional Navigation Components
42    "simple_jjj_navigation" => Some(simple_jjj_navigation_component()),
43    "search_cargo" => Some(search_cargo_component()),
44    "advanced_navigation" => Some(advanced_navigation_component()),
45    "multi_select" => Some(multi_select_component()),
46    "split_view" => Some(split_view_component()),
47
48    // UI Components
49    "intro_message" => Some(intro_message_component()),
50    "final_message" => Some(final_message_component()),
51    "final_message_short" => Some(final_message_short_component()),
52
53    _ => None,
54  }
55}
56
57// List all available components
58pub fn list_all_components() -> Vec<DemoComponent> {
59  vec![
60    // Navigation Components
61    basic_navigation_component(),
62    word_navigation_component(),
63    paragraph_navigation_component(),
64    search_navigation_component(),
65    // Selection Components
66    select_word_component(),
67    select_paragraph_component(),
68    select_line_component(),
69    visual_char_mode_component(),
70    // Action Components
71    yank_line_component(),
72    yank_selection_component(),
73    highlight_selection_component(),
74    clear_highlights_component(),
75    // Command Components
76    execute_ls_component(),
77    execute_cat_component(),
78    execute_grep_component(),
79    yank_and_execute_component(),
80    execute_cat_with_paste_component(),
81    // Additional Navigation Components
82    simple_jjj_navigation_component(),
83    search_cargo_component(),
84    advanced_navigation_component(),
85    multi_select_component(),
86    split_view_component(),
87    // UI Components
88    intro_message_component(),
89    final_message_component(),
90    final_message_short_component(),
91  ]
92}
93
94// ===== Navigation Components =====
95
96fn basic_navigation_component() -> DemoComponent {
97  use DemoAction::*;
98
99  DemoComponent {
100    id: "basic_navigation",
101    name: "Basic Navigation",
102    description: "Basic j/k/h/l movements",
103    actions: vec![
104      ShowHint(
105        "navigate with vim keys\nj=down k=up h=left l=right".to_string(),
106        Duration::from_millis(3000),
107      ),
108      Wait(Duration::from_millis(500)),
109      Key(KeyCode::Char('j')),
110      Wait(Duration::from_millis(300)),
111      Key(KeyCode::Char('j')),
112      Wait(Duration::from_millis(300)),
113      Key(KeyCode::Char('k')),
114      Wait(Duration::from_millis(300)),
115      Key(KeyCode::Char('l')),
116      Wait(Duration::from_millis(300)),
117      Key(KeyCode::Char('h')),
118      Wait(Duration::from_millis(500)),
119    ],
120  }
121}
122
123fn word_navigation_component() -> DemoComponent {
124  use DemoAction::*;
125
126  DemoComponent {
127    id: "word_navigation",
128    name: "Word Navigation",
129    description: "w/b/e word movements",
130    actions: vec![
131      ShowHint(
132        "jump between words\nw=next b=back e=end".to_string(),
133        Duration::from_millis(3000),
134      ),
135      Wait(Duration::from_millis(500)),
136      Key(KeyCode::Char('w')),
137      Wait(Duration::from_millis(300)),
138      Key(KeyCode::Char('w')),
139      Wait(Duration::from_millis(300)),
140      Key(KeyCode::Char('b')),
141      Wait(Duration::from_millis(300)),
142      Key(KeyCode::Char('e')),
143      Wait(Duration::from_millis(500)),
144    ],
145  }
146}
147
148fn paragraph_navigation_component() -> DemoComponent {
149  use DemoAction::*;
150
151  DemoComponent {
152    id: "paragraph_navigation",
153    name: "Paragraph Navigation",
154    description: "{ } paragraph jumps",
155    actions: vec![
156      ShowHint(
157        "jump between paragraphs\n{ = previous } = next".to_string(),
158        Duration::from_millis(3000),
159      ),
160      Wait(Duration::from_millis(500)),
161      Key(KeyCode::Char('}')),
162      Wait(Duration::from_millis(500)),
163      Key(KeyCode::Char('}')),
164      Wait(Duration::from_millis(500)),
165      Key(KeyCode::Char('{')),
166      Wait(Duration::from_millis(500)),
167    ],
168  }
169}
170
171fn search_navigation_component() -> DemoComponent {
172  use DemoAction::*;
173
174  DemoComponent {
175    id: "search_navigation",
176    name: "Search Navigation",
177    description: "/ search and n/N navigation",
178    actions: vec![
179      ShowHint(
180        "search for text\n/ to search, n/N to navigate".to_string(),
181        Duration::from_millis(3000),
182      ),
183      Wait(Duration::from_millis(500)),
184      Key(KeyCode::Char('/')),
185      Wait(Duration::from_millis(250)),
186      TypeString("reader".to_string(), Duration::from_millis(100)),
187      Wait(Duration::from_millis(250)),
188      Key(KeyCode::Enter),
189      Wait(Duration::from_millis(500)),
190      Key(KeyCode::Char('n')),
191      Wait(Duration::from_millis(500)),
192      Key(KeyCode::Char('N')),
193      Wait(Duration::from_millis(500)),
194    ],
195  }
196}
197
198// ===== Selection Components =====
199
200fn select_word_component() -> DemoComponent {
201  use DemoAction::*;
202
203  DemoComponent {
204    id: "select_word",
205    name: "Select Word",
206    description: "viw to select word",
207    actions: vec![
208      ShowHint(
209        "select entire words\nwith text objects".to_string(),
210        Duration::from_millis(3000),
211      ),
212      Wait(Duration::from_millis(500)),
213      VimMotion("viw".to_string()),
214      Wait(Duration::from_millis(1000)),
215      Key(KeyCode::Esc),
216      Wait(Duration::from_millis(500)),
217    ],
218  }
219}
220
221fn select_paragraph_component() -> DemoComponent {
222  use DemoAction::*;
223
224  DemoComponent {
225    id: "select_paragraph",
226    name: "Select Paragraph",
227    description: "vip to select paragraph",
228    actions: vec![
229      ShowHint(
230        "select entire paragraphs".to_string(),
231        Duration::from_millis(3500),
232      ),
233      Wait(Duration::from_millis(500)),
234      VimMotion("vip".to_string()),
235      Wait(Duration::from_millis(2000)),
236    ],
237  }
238}
239
240fn select_line_component() -> DemoComponent {
241  use DemoAction::*;
242
243  DemoComponent {
244    id: "select_line",
245    name: "Select Line",
246    description: "V line selection",
247    actions: vec![
248      ShowHint(
249        "select entire lines\nwith visual line mode".to_string(),
250        Duration::from_millis(3000),
251      ),
252      Wait(Duration::from_millis(500)),
253      Key(KeyCode::Char('V')),
254      Wait(Duration::from_millis(500)),
255      Key(KeyCode::Char('j')),
256      Wait(Duration::from_millis(500)),
257      Key(KeyCode::Char('j')),
258      Wait(Duration::from_millis(1000)),
259    ],
260  }
261}
262
263fn visual_char_mode_component() -> DemoComponent {
264  use DemoAction::*;
265
266  DemoComponent {
267    id: "visual_char_mode",
268    name: "Visual Character Mode",
269    description: "v + character selection",
270    actions: vec![
271      ShowHint(
272        "precise character selection\nwith visual mode".to_string(),
273        Duration::from_millis(3000),
274      ),
275      Wait(Duration::from_millis(500)),
276      Key(KeyCode::Char('v')),
277      Wait(Duration::from_millis(500)),
278      Key(KeyCode::Char('w')),
279      Wait(Duration::from_millis(300)),
280      Key(KeyCode::Char('w')),
281      Wait(Duration::from_millis(300)),
282      Key(KeyCode::Char('e')),
283      Wait(Duration::from_millis(1000)),
284    ],
285  }
286}
287
288// ===== Action Components =====
289
290fn yank_line_component() -> DemoComponent {
291  use DemoAction::*;
292
293  DemoComponent {
294    id: "yank_line",
295    name: "Yank Line",
296    description: "yy to yank line",
297    actions: vec![
298      ShowHint(
299        "copy from command output".to_string(),
300        Duration::from_millis(3000),
301      ),
302      Wait(Duration::from_millis(500)),
303      Key(KeyCode::Char('y')),
304      Wait(Duration::from_millis(250)),
305      Key(KeyCode::Char('y')),
306      Wait(Duration::from_millis(1000)),
307    ],
308  }
309}
310
311fn yank_selection_component() -> DemoComponent {
312  use DemoAction::*;
313
314  DemoComponent {
315    id: "yank_selection",
316    name: "Yank Selection",
317    description: "y to yank selection",
318    actions: vec![
319      ShowHint(
320        "copy selected text\nwith y command".to_string(),
321        Duration::from_millis(3000),
322      ),
323      Wait(Duration::from_millis(500)),
324      Key(KeyCode::Char('y')),
325      Wait(Duration::from_millis(1000)),
326    ],
327  }
328}
329
330fn highlight_selection_component() -> DemoComponent {
331  use DemoAction::*;
332
333  DemoComponent {
334    id: "highlight_selection",
335    name: "Highlight Selection",
336    description: ":h to highlight",
337    actions: vec![
338      ShowHint(
339        "highlight selected text".to_string(),
340        Duration::from_millis(3500),
341      ),
342      Wait(Duration::from_millis(500)),
343      Key(KeyCode::Char(':')),
344      Wait(Duration::from_millis(250)),
345      Key(KeyCode::Char('h')),
346      Wait(Duration::from_millis(500)),
347      Key(KeyCode::Enter),
348      Wait(Duration::from_millis(1500)),
349    ],
350  }
351}
352
353fn clear_highlights_component() -> DemoComponent {
354  use DemoAction::*;
355
356  DemoComponent {
357    id: "clear_highlights",
358    name: "Clear Highlights",
359    description: ":ch to clear highlights",
360    actions: vec![
361      ShowHint(
362        "clear all highlights\nwith :ch command".to_string(),
363        Duration::from_millis(3000),
364      ),
365      Wait(Duration::from_millis(500)),
366      Key(KeyCode::Char(':')),
367      Wait(Duration::from_millis(250)),
368      Key(KeyCode::Char('c')),
369      Wait(Duration::from_millis(250)),
370      Key(KeyCode::Char('h')),
371      Wait(Duration::from_millis(500)),
372      Key(KeyCode::Enter),
373      Wait(Duration::from_millis(1000)),
374    ],
375  }
376}
377
378// ===== Command Components =====
379
380fn execute_ls_component() -> DemoComponent {
381  use DemoAction::*;
382
383  DemoComponent {
384    id: "execute_ls",
385    name: "Execute ls Command",
386    description: "List files with :!ls",
387    actions: vec![
388      ShowHint("execute commands".to_string(), Duration::from_millis(3500)),
389      Wait(Duration::from_millis(500)),
390      Key(KeyCode::Char(':')),
391      Wait(Duration::from_millis(250)),
392      Key(KeyCode::Char('!')),
393      Wait(Duration::from_millis(250)),
394      Key(KeyCode::Char('l')),
395      Wait(Duration::from_millis(250)),
396      Key(KeyCode::Char('s')),
397      Wait(Duration::from_millis(500)),
398      Key(KeyCode::Enter),
399      Wait(Duration::from_millis(1000)),
400    ],
401  }
402}
403
404fn execute_cat_component() -> DemoComponent {
405  use DemoAction::*;
406
407  DemoComponent {
408    id: "execute_cat",
409    name: "Execute cat Command",
410    description: ":!cat filename",
411    actions: vec![
412      ShowHint(
413        "view file contents\nwith :!cat command".to_string(),
414        Duration::from_millis(3500),
415      ),
416      Wait(Duration::from_millis(500)),
417      Key(KeyCode::Char(':')),
418      Wait(Duration::from_millis(250)),
419      Key(KeyCode::Char('!')),
420      Wait(Duration::from_millis(250)),
421      TypeString("cat README.md".to_string(), Duration::from_millis(100)),
422      Wait(Duration::from_millis(500)),
423      Key(KeyCode::Enter),
424      Wait(Duration::from_millis(1500)),
425    ],
426  }
427}
428
429fn execute_grep_component() -> DemoComponent {
430  use DemoAction::*;
431
432  DemoComponent {
433    id: "execute_grep",
434    name: "Execute grep Command",
435    description: ":!grep pattern file",
436    actions: vec![
437      ShowHint(
438        "search file contents\nwith :!grep command".to_string(),
439        Duration::from_millis(3500),
440      ),
441      Wait(Duration::from_millis(500)),
442      Key(KeyCode::Char(':')),
443      Wait(Duration::from_millis(250)),
444      Key(KeyCode::Char('!')),
445      Wait(Duration::from_millis(250)),
446      TypeString("grep TODO *.md".to_string(), Duration::from_millis(100)),
447      Wait(Duration::from_millis(500)),
448      Key(KeyCode::Enter),
449      Wait(Duration::from_millis(1500)),
450    ],
451  }
452}
453
454fn yank_and_execute_component() -> DemoComponent {
455  use DemoAction::*;
456
457  DemoComponent {
458    id: "yank_and_execute",
459    name: "Yank and Execute",
460    description: "Yank then paste in command",
461    actions: vec![
462      ShowHint(
463        "paste yanked text\ninto commands with Ctrl+V".to_string(),
464        Duration::from_millis(3500),
465      ),
466      Wait(Duration::from_millis(500)),
467      Key(KeyCode::Char(':')),
468      Wait(Duration::from_millis(250)),
469      Key(KeyCode::Char('!')),
470      Wait(Duration::from_millis(250)),
471      Key(KeyCode::Char('c')),
472      Wait(Duration::from_millis(250)),
473      Key(KeyCode::Char('a')),
474      Wait(Duration::from_millis(250)),
475      Key(KeyCode::Char('t')),
476      Wait(Duration::from_millis(250)),
477      Key(KeyCode::Char(' ')),
478      Wait(Duration::from_millis(250)),
479      KeyWithModifiers(KeyCode::Char('v'), KeyModifiers::CONTROL),
480      Wait(Duration::from_millis(500)),
481      Key(KeyCode::Enter),
482      Wait(Duration::from_millis(2000)),
483    ],
484  }
485}
486
487// ===== UI Components =====
488
489fn intro_message_component() -> DemoComponent {
490  use DemoAction::*;
491
492  DemoComponent {
493    id: "intro_message",
494    name: "Intro Message",
495    description: "Opening message",
496    actions: vec![Wait(Duration::from_millis(2000))],
497  }
498}
499
500fn final_message_component() -> DemoComponent {
501  use DemoAction::*;
502
503  DemoComponent {
504    id: "final_message",
505    name: "Final Message",
506    description: "Closing with github link",
507    actions: vec![
508      ShowHint(
509        "hygg - simplifying the way you read\n\ngithub.com/kruseio/hygg"
510          .to_string(),
511        Duration::from_millis(5000),
512      ),
513      Wait(Duration::from_millis(4000)),
514    ],
515  }
516}
517
518fn final_message_short_component() -> DemoComponent {
519  use DemoAction::*;
520
521  DemoComponent {
522    id: "final_message_short",
523    name: "Final Message Short",
524    description: "Short closing message",
525    actions: vec![
526      ShowHint(
527        "hygg - simplifying the way you read\n\ngithub.com/kruseio/hygg"
528          .to_string(),
529        Duration::from_millis(3000),
530      ),
531      Wait(Duration::from_millis(2000)),
532    ],
533  }
534}
535
536// ===== Additional Navigation Components =====
537
538fn simple_jjj_navigation_component() -> DemoComponent {
539  use DemoAction::*;
540
541  DemoComponent {
542    id: "simple_jjj_navigation",
543    name: "Simple JJJ Navigation",
544    description: "Just 3 j movements down",
545    actions: vec![
546      ShowHint(
547        "navigate to the file listing\nusing j to move down".to_string(),
548        Duration::from_millis(3000),
549      ),
550      Wait(Duration::from_millis(500)),
551      Key(KeyCode::Char('j')),
552      Wait(Duration::from_millis(300)),
553      Key(KeyCode::Char('j')),
554      Wait(Duration::from_millis(300)),
555      Key(KeyCode::Char('j')),
556      Wait(Duration::from_millis(500)),
557    ],
558  }
559}
560
561fn search_cargo_component() -> DemoComponent {
562  use DemoAction::*;
563
564  DemoComponent {
565    id: "search_cargo",
566    name: "Search in command output",
567    description: "Search in command output",
568    actions: vec![
569      ShowHint(
570        "search in command output".to_string(),
571        Duration::from_millis(3000),
572      ),
573      Wait(Duration::from_millis(500)),
574      Key(KeyCode::Char('/')),
575      Wait(Duration::from_millis(250)),
576      TypeString("toml".to_string(), Duration::from_millis(100)),
577      Wait(Duration::from_millis(500)),
578      Key(KeyCode::Enter),
579      Wait(Duration::from_millis(1000)),
580    ],
581  }
582}
583
584// ===== Additional Command Components =====
585
586fn execute_cat_with_paste_component() -> DemoComponent {
587  use DemoAction::*;
588
589  DemoComponent {
590    id: "execute_cat_with_paste",
591    name: "Execute cat with Paste",
592    description: ":!cat with Ctrl+V to paste yanked text",
593    actions: vec![
594      ShowHint(
595        "paste into next command".to_string(),
596        Duration::from_millis(3500),
597      ),
598      Wait(Duration::from_millis(500)),
599      Key(KeyCode::Char(':')),
600      Wait(Duration::from_millis(250)),
601      Key(KeyCode::Char('!')),
602      Wait(Duration::from_millis(250)),
603      Key(KeyCode::Char('c')),
604      Wait(Duration::from_millis(100)),
605      Key(KeyCode::Char('a')),
606      Wait(Duration::from_millis(100)),
607      Key(KeyCode::Char('t')),
608      Wait(Duration::from_millis(100)),
609      Key(KeyCode::Char(' ')),
610      Wait(Duration::from_millis(250)),
611      KeyWithModifiers(KeyCode::Char('v'), KeyModifiers::CONTROL),
612      Wait(Duration::from_millis(500)),
613      Key(KeyCode::Enter),
614      Wait(Duration::from_millis(2000)),
615    ],
616  }
617}
618
619fn advanced_navigation_component() -> DemoComponent {
620  use DemoAction::*;
621
622  DemoComponent {
623    id: "advanced_navigation",
624    name: "Advanced Navigation",
625    description: "gg/G/Ctrl-f/Ctrl-b movements",
626    actions: vec![
627      ShowHint(
628        "advanced navigation\ngg=top G=bottom Ctrl-f/b=page".to_string(),
629        Duration::from_millis(3500),
630      ),
631      Wait(Duration::from_millis(500)),
632      VimMotion("gg".to_string()),
633      Wait(Duration::from_millis(500)),
634      Key(KeyCode::Char('G')),
635      Wait(Duration::from_millis(500)),
636      KeyWithModifiers(KeyCode::Char('f'), KeyModifiers::CONTROL),
637      Wait(Duration::from_millis(500)),
638      KeyWithModifiers(KeyCode::Char('b'), KeyModifiers::CONTROL),
639      Wait(Duration::from_millis(500)),
640    ],
641  }
642}
643
644fn multi_select_component() -> DemoComponent {
645  use DemoAction::*;
646
647  DemoComponent {
648    id: "multi_select",
649    name: "Multi Select",
650    description: "Multiple visual selections demo",
651    actions: vec![
652      ShowHint(
653        "select multiple sections\nwith visual mode".to_string(),
654        Duration::from_millis(3000),
655      ),
656      Wait(Duration::from_millis(500)),
657      Key(KeyCode::Char('v')),
658      Wait(Duration::from_millis(300)),
659      Key(KeyCode::Char('}')),
660      Wait(Duration::from_millis(500)),
661      Key(KeyCode::Char('}')),
662      Wait(Duration::from_millis(500)),
663      Key(KeyCode::Esc),
664      Wait(Duration::from_millis(500)),
665    ],
666  }
667}
668
669fn split_view_component() -> DemoComponent {
670  use DemoAction::*;
671
672  DemoComponent {
673    id: "split_view",
674    name: "Split View",
675    description: "Demo split view after command execution",
676    actions: vec![
677      ShowHint(
678        "command output appears\nin a split view".to_string(),
679        Duration::from_millis(3000),
680      ),
681      Wait(Duration::from_millis(500)),
682      // The split view appears automatically after command execution
683      // Just demonstrate navigation in split view
684      Key(KeyCode::Char('j')),
685      Wait(Duration::from_millis(300)),
686      Key(KeyCode::Char('k')),
687      Wait(Duration::from_millis(500)),
688    ],
689  }
690}
691
692#[cfg(test)]
693mod tests {
694  use super::*;
695
696  #[test]
697  fn test_get_component_valid() {
698    let component = get_component("basic_navigation");
699    assert!(component.is_some());
700    let component = component.unwrap();
701    assert_eq!(component.id, "basic_navigation");
702    assert_eq!(component.name, "Basic Navigation");
703  }
704
705  #[test]
706  fn test_get_component_invalid() {
707    let component = get_component("nonexistent_component");
708    assert!(component.is_none());
709  }
710
711  #[test]
712  fn test_list_all_components_count() {
713    let components = list_all_components();
714    // We have 22 components total
715    assert!(components.len() >= 22);
716  }
717
718  #[test]
719  fn test_component_actions_not_empty() {
720    let component = get_component("select_paragraph").unwrap();
721    assert!(!component.actions.is_empty());
722  }
723
724  #[test]
725  fn test_new_components_exist() {
726    assert!(get_component("simple_jjj_navigation").is_some());
727    assert!(get_component("search_cargo").is_some());
728    assert!(get_component("execute_cat_with_paste").is_some());
729    assert!(get_component("advanced_navigation").is_some());
730    assert!(get_component("multi_select").is_some());
731    assert!(get_component("split_view").is_some());
732  }
733}