cli_text_reader/
demo_script.rs

1use crate::demo_components::get_component;
2use crossterm::event::{KeyCode, KeyModifiers};
3use std::time::Duration;
4
5#[derive(Clone, Debug)]
6#[allow(dead_code)]
7pub enum DemoAction {
8  // Wait for a duration
9  Wait(Duration),
10  // Press a single key
11  Key(KeyCode),
12  // Press a key with modifiers
13  KeyWithModifiers(KeyCode, KeyModifiers),
14  // Type a string character by character
15  TypeString(String, Duration), // string, delay between chars
16  // Show a subtitle/hint
17  ShowHint(String, Duration), // hint text, display duration
18  // Mark a checkpoint (for debugging)
19  Checkpoint(String),
20  // Execute a vim motion (e.g., "vip", "viw", "yy")
21  VimMotion(String),
22}
23
24#[allow(dead_code)]
25pub struct DemoScript {
26  pub actions: Vec<DemoAction>,
27}
28
29impl DemoScript {
30  // Build demo from component IDs
31  pub fn from_components(component_ids: &[&str]) -> Self {
32    let mut actions = vec![];
33
34    for id in component_ids {
35      if let Some(component) = get_component(id) {
36        actions.extend(component.actions);
37      } else {
38        eprintln!("Warning: Unknown demo component ID: {id}");
39      }
40    }
41
42    Self { actions }
43  }
44
45  pub fn marketing_demo() -> Self {
46    Self::from_components(&[
47      "intro_message",
48      "select_paragraph",
49      "highlight_selection",
50      "execute_ls",
51      "search_cargo",
52      "yank_line",
53      "execute_cat_with_paste",
54      // "final_message",
55    ])
56  }
57
58  pub fn speed_demo() -> Self {
59    Self::from_components(&[
60      "intro_message",
61      "basic_navigation",
62      "word_navigation",
63      "select_paragraph",
64      "highlight_selection",
65      "final_message_short",
66    ])
67  }
68
69  pub fn power_user_demo() -> Self {
70    Self::from_components(&[
71      "intro_message",
72      "paragraph_navigation",
73      "search_navigation",
74      "visual_char_mode",
75      "yank_selection",
76      "execute_ls",
77      "yank_and_execute",
78      "execute_grep",
79      "final_message",
80    ])
81  }
82
83  pub fn minimal_demo() -> Self {
84    Self::from_components(&[
85      "intro_message",
86      "basic_navigation",
87      "select_word",
88      "highlight_selection",
89      "clear_highlights",
90      "final_message_short",
91    ])
92  }
93
94  pub fn workflow_demo() -> Self {
95    Self::from_components(&[
96      "intro_message",
97      "basic_navigation",
98      "visual_char_mode",
99      "yank_selection",
100      "execute_cat",
101      "select_line",
102      "highlight_selection",
103      "final_message",
104    ])
105  }
106
107  #[allow(dead_code)]
108  pub fn beginner_tutorial() -> Self {
109    use DemoAction::*;
110
111    let actions = vec![
112      // Welcome
113      ShowHint(
114        "Welcome to hygg! Let's learn the basics.".to_string(),
115        Duration::from_millis(3000),
116      ),
117      Wait(Duration::from_millis(1000)),
118      // Basic movement
119      ShowHint(
120        "Use j/k to move down/up (try it now!)".to_string(),
121        Duration::from_millis(5000),
122      ),
123      Checkpoint("Waiting for user to practice j/k".to_string()),
124      // More to be added based on user interaction
125    ];
126
127    Self { actions }
128  }
129
130  // Test script for the interactive tutorial
131  #[allow(dead_code)]
132  pub fn tutorial_test() -> Self {
133    use DemoAction::*;
134
135    let actions = vec![
136      // Wait for initial load
137      Wait(Duration::from_millis(500)),
138      // Step 1: Welcome - press 'j' to move down
139      Wait(Duration::from_millis(500)),
140      Key(KeyCode::Char('j')),
141      Wait(Duration::from_millis(500)),
142      // Type :next to continue
143      Key(KeyCode::Char(':')),
144      Wait(Duration::from_millis(100)),
145      Key(KeyCode::Char('n')),
146      Key(KeyCode::Char('e')),
147      Key(KeyCode::Char('x')),
148      Key(KeyCode::Char('t')),
149      Key(KeyCode::Enter),
150      Wait(Duration::from_millis(500)),
151      // Step 2: Visual Selection - press 'v' and move to select, then 'y' to
152      // yank
153      Key(KeyCode::Char('v')),
154      Wait(Duration::from_millis(200)),
155      Key(KeyCode::Char('w')),
156      Wait(Duration::from_millis(200)),
157      Key(KeyCode::Char('y')),
158      Wait(Duration::from_millis(500)),
159      // Type :next
160      Key(KeyCode::Char(':')),
161      Wait(Duration::from_millis(100)),
162      Key(KeyCode::Char('n')),
163      Key(KeyCode::Char('e')),
164      Key(KeyCode::Char('x')),
165      Key(KeyCode::Char('t')),
166      Key(KeyCode::Enter),
167      Wait(Duration::from_millis(500)),
168      // Step 3: Text Objects - vip to select paragraph, then :h to highlight
169      VimMotion("vip".to_string()),
170      Wait(Duration::from_millis(500)),
171      Key(KeyCode::Char(':')),
172      Wait(Duration::from_millis(100)),
173      Key(KeyCode::Char('h')),
174      Key(KeyCode::Enter),
175      Wait(Duration::from_millis(500)),
176      // Type :next
177      Key(KeyCode::Char(':')),
178      Wait(Duration::from_millis(100)),
179      Key(KeyCode::Char('n')),
180      Key(KeyCode::Char('e')),
181      Key(KeyCode::Char('x')),
182      Key(KeyCode::Char('t')),
183      Key(KeyCode::Enter),
184      Wait(Duration::from_millis(500)),
185      // Step 4: Search - search for "special"
186      Key(KeyCode::Char('/')),
187      Wait(Duration::from_millis(200)),
188      Key(KeyCode::Char('s')),
189      Key(KeyCode::Char('p')),
190      Key(KeyCode::Char('e')),
191      Key(KeyCode::Char('c')),
192      Key(KeyCode::Char('i')),
193      Key(KeyCode::Char('a')),
194      Key(KeyCode::Char('l')),
195      Key(KeyCode::Enter),
196      Wait(Duration::from_millis(500)),
197      // Type :next
198      Key(KeyCode::Char(':')),
199      Wait(Duration::from_millis(100)),
200      Key(KeyCode::Char('n')),
201      Key(KeyCode::Char('e')),
202      Key(KeyCode::Char('x')),
203      Key(KeyCode::Char('t')),
204      Key(KeyCode::Enter),
205      Wait(Duration::from_millis(500)),
206      // Step 5: Bookmarks - set bookmark 'a' with 'ma'
207      Key(KeyCode::Char('m')),
208      Wait(Duration::from_millis(200)),
209      Key(KeyCode::Char('a')),
210      Wait(Duration::from_millis(500)),
211      // Type :next
212      Key(KeyCode::Char(':')),
213      Wait(Duration::from_millis(100)),
214      Key(KeyCode::Char('n')),
215      Key(KeyCode::Char('e')),
216      Key(KeyCode::Char('x')),
217      Key(KeyCode::Char('t')),
218      Key(KeyCode::Enter),
219      Wait(Duration::from_millis(500)),
220      // Step 6: Command Execution - run :!echo hello world
221      Key(KeyCode::Char(':')),
222      Wait(Duration::from_millis(100)),
223      Key(KeyCode::Char('!')),
224      Key(KeyCode::Char('e')),
225      Key(KeyCode::Char('c')),
226      Key(KeyCode::Char('h')),
227      Key(KeyCode::Char('o')),
228      Key(KeyCode::Char(' ')),
229      Key(KeyCode::Char('h')),
230      Key(KeyCode::Char('e')),
231      Key(KeyCode::Char('l')),
232      Key(KeyCode::Char('l')),
233      Key(KeyCode::Char('o')),
234      Key(KeyCode::Char(' ')),
235      Key(KeyCode::Char('w')),
236      Key(KeyCode::Char('o')),
237      Key(KeyCode::Char('r')),
238      Key(KeyCode::Char('l')),
239      Key(KeyCode::Char('d')),
240      Key(KeyCode::Enter),
241      Wait(Duration::from_millis(1000)), // Wait for command execution
242      // Type :next
243      Key(KeyCode::Char(':')),
244      Wait(Duration::from_millis(100)),
245      Key(KeyCode::Char('n')),
246      Key(KeyCode::Char('e')),
247      Key(KeyCode::Char('x')),
248      Key(KeyCode::Char('t')),
249      Key(KeyCode::Enter),
250      Wait(Duration::from_millis(500)),
251      // Final step: Press Enter to complete tutorial
252      Key(KeyCode::Enter),
253      Wait(Duration::from_millis(1000)),
254    ];
255
256    Self { actions }
257  }
258}
259
260#[cfg(test)]
261mod tests {
262  use super::*;
263
264  #[test]
265  fn test_from_components() {
266    let demo = DemoScript::from_components(&["intro_message", "final_message"]);
267    assert!(!demo.actions.is_empty());
268  }
269
270  #[test]
271  fn test_marketing_demo_has_correct_components() {
272    let demo = DemoScript::marketing_demo();
273    assert!(!demo.actions.is_empty());
274  }
275
276  #[test]
277  fn test_from_components_with_invalid_id() {
278    // Should handle invalid component IDs gracefully
279    let demo = DemoScript::from_components(&[
280      "intro_message",
281      "invalid_component",
282      "final_message",
283    ]);
284    // Should still have actions from valid components
285    assert!(!demo.actions.is_empty());
286  }
287}