oxur_cli/repl/
help.rs

1//! Help system for Oxur REPL
2//!
3//! Provides beautiful tiered help with:
4//! - Overview via (help)
5//! - Detailed topics via (help <topic>)
6//! - Color-formatted output
7
8use std::collections::HashMap;
9
10/// Help system for the Oxur REPL
11///
12/// Provides tiered help documentation with beautiful formatting.
13pub struct HelpSystem {
14    topics: HashMap<String, String>,
15    color_enabled: bool,
16}
17
18impl HelpSystem {
19    /// Create a new help system
20    ///
21    /// # Arguments
22    ///
23    /// * `color_enabled` - Whether to use ANSI color codes in output
24    pub fn new(color_enabled: bool) -> Self {
25        let mut topics = HashMap::new();
26
27        // Initialize all topics
28        topics.insert("basics".to_string(), Self::generate_basics_topic(color_enabled));
29        topics.insert("evaluation".to_string(), Self::generate_evaluation_topic(color_enabled));
30        topics.insert("keyboard".to_string(), Self::generate_keyboard_topic(color_enabled));
31        topics.insert("sessions".to_string(), Self::generate_sessions_topic(color_enabled));
32        topics.insert("commands".to_string(), Self::generate_commands_topic(color_enabled));
33        topics.insert("modes".to_string(), Self::generate_modes_topic(color_enabled));
34        topics.insert("performance".to_string(), Self::generate_performance_topic(color_enabled));
35        topics.insert("stats".to_string(), Self::generate_stats_topic(color_enabled));
36
37        Self { topics, color_enabled }
38    }
39
40    /// Show the main help overview
41    pub fn show_overview(&self) -> String {
42        let mut output = String::new();
43
44        // Header
45        output.push_str(&self.header("Oxur REPL Help"));
46        output.push('\n');
47
48        // Getting Started
49        output.push_str(&self.section("GETTING STARTED"));
50        output.push_str("Type any Lisp expression at the prompt to evaluate it:\n");
51        output.push_str(&self.example("  (+ 1 2)", Some("3")));
52        output.push_str(&self.example("  (* 6 7)", Some("42")));
53        output.push_str(&self.example("  (println! \"Hi\")", Some("prints and returns ()")));
54        output.push('\n');
55
56        // Control Commands
57        output.push_str(&self.section("CONTROL COMMANDS"));
58        output.push_str(&self.command("(banner)", "Redisplay the welcome banner"));
59        output.push_str(&self.command("(clear)", "Clear the terminal screen"));
60        output.push_str(&self.command("(help)", "Show this help overview"));
61        output.push_str(&self.command("(help <topic>)", "Show detailed help for a topic"));
62        output.push_str(&self.command("(info)", "Show system metadata (versions, platform, etc.)"));
63        output.push_str(&self.command("(quit), (q), (exit)", "Exit the REPL"));
64        output.push_str(&self.command("(stats)", "Show session statistics"));
65        output.push_str(&self.command(
66            "(stats <view>)",
67            "Show specific stats view (views: execution, cache, resources, server, subprocess)",
68        ));
69        output.push('\n');
70
71        // Session Management Commands
72        output.push_str(&self.section("SESSION MANAGEMENT"));
73        output.push_str(&self.command("(sessions)", "List all active sessions"));
74        output.push_str(&self.command("(current-session)", "Show current session ID"));
75        output.push_str(&self.command("(new-session)", "Create and switch to a new session"));
76        output.push_str(
77            &self.command("(new-session \"name\")", "Create and switch to a named session"),
78        );
79        output.push_str(&self.command("(switch-session <id>)", "Switch to an existing session"));
80        output.push_str(
81            &self.command("(close-session)", "Close current session (must switch first)"),
82        );
83        output.push_str(&self.command("(close-session <id>)", "Close a specific session"));
84        output.push('\n');
85        output.push_str(&self.command("Ctrl-C", "Cancel current input line"));
86        output.push_str(&self.command("Ctrl-D", "Exit the REPL (EOF)"));
87        output.push('\n');
88
89        // Keyboard Shortcuts
90        output.push_str(&self.section("KEYBOARD SHORTCUTS"));
91        output.push_str(&self.command("↑ / ↓", "Navigate command history"));
92        output.push_str(&self.command("Ctrl-A", "Move to start of line"));
93        output.push_str(&self.command("Ctrl-E", "Move to end of line"));
94        output.push_str(&self.command("Ctrl-K", "Kill text from cursor to end"));
95        output.push_str(&self.command("Ctrl-U", "Kill text from start to cursor"));
96        output.push_str(&self.command("Ctrl-R", "Search history (incremental)"));
97        output.push('\n');
98
99        // Evaluation
100        output.push_str(&self.section("EVALUATION"));
101        output.push_str("Oxur uses three-tier execution for optimal performance:\n");
102        output.push_str(&self.command("Tier 1: Calculator", "(~1ms)     - Simple arithmetic"));
103        output.push_str(&self.command("Tier 2: Cached JIT", "(~1-5ms)   - Previously compiled"));
104        output.push_str(&self.command("Tier 3: Fresh JIT", "(~50-300ms) - New compilation"));
105        output.push('\n');
106        output.push_str(&self.note("⚡ TIP: Repeated evaluations are cached for speed!"));
107        output.push('\n');
108
109        // Help Topics
110        output.push_str(&self.section("HELP TOPICS"));
111        output.push_str(&self.command("basics", "Getting started with Oxur"));
112        output.push_str(&self.command("evaluation", "How evaluation works in detail"));
113        output.push_str(&self.command("keyboard", "All keyboard shortcuts and editing"));
114        output.push_str(&self.command("sessions", "Session management and modes"));
115        output.push_str(&self.command("commands", "All special commands reference"));
116        output.push_str(&self.command("modes", "Lisp vs Sexpr evaluation modes"));
117        output.push_str(&self.command("performance", "Understanding execution tiers"));
118        output.push_str(&self.command("stats", "Statistics and instrumentation"));
119        output.push('\n');
120        output.push_str("Type ");
121        output.push_str(&self.cmd_text("(help <topic>)"));
122        output.push_str(" for detailed information on any topic.\n");
123        output.push('\n');
124        output.push_str("For more information, visit: ");
125        output.push_str(&self.cmd_text("http://oxur.li/"));
126        output.push('\n');
127
128        output
129    }
130
131    /// Show help for a specific topic
132    ///
133    /// Returns None if the topic doesn't exist
134    pub fn show_topic(&self, topic: &str) -> Option<String> {
135        self.topics.get(topic).cloned()
136    }
137
138    /// List all available help topics
139    #[allow(dead_code)]
140    pub fn list_topics(&self) -> Vec<&str> {
141        let mut topics: Vec<&str> = self.topics.keys().map(|s| s.as_str()).collect();
142        topics.sort_unstable();
143        topics
144    }
145
146    // ========================================================================
147    // Formatting Helpers
148    // ========================================================================
149
150    /// Format a header with box drawing
151    fn header(&self, text: &str) -> String {
152        let width = text.len() + 4;
153        let top = format!("╭─{}─╮\n", "─".repeat(width - 2));
154        let middle = format!("│  {}{}{}  │\n", self.bold_cyan(), text, self.reset());
155        let bottom = format!("╰─{}─╯\n", "─".repeat(width - 2));
156        format!("{}{}{}", top, middle, bottom)
157    }
158
159    /// Format a section header
160    fn section(&self, title: &str) -> String {
161        format!("{}{}{}\n{}\n", self.bold_cyan(), title, self.reset(), self.divider())
162    }
163
164    /// Format a command with description
165    fn command(&self, cmd: &str, desc: &str) -> String {
166        format!("  {}{:<20}{}  {}\n", self.yellow(), cmd, self.reset(), desc)
167    }
168
169    /// Format an example with optional result
170    fn example(&self, code: &str, result: Option<&str>) -> String {
171        if let Some(res) = result {
172            format!("{}{}{}  → {}\n", self.green(), code, self.reset(), res)
173        } else {
174            format!("{}{}{}\n", self.green(), code, self.reset())
175        }
176    }
177
178    /// Format command text (inline)
179    fn cmd_text(&self, text: &str) -> String {
180        format!("{}{}{}", self.yellow(), text, self.reset())
181    }
182
183    /// Format a divider line
184    fn divider(&self) -> String {
185        format!("{}{}{}\n", self.dim(), "─".repeat(60), self.reset())
186    }
187
188    /// Format a note or tip
189    fn note(&self, text: &str) -> String {
190        format!("{}{}{}\n", self.italic_cyan(), text, self.reset())
191    }
192
193    // ========================================================================
194    // ANSI Color Codes
195    // ========================================================================
196
197    fn bold_cyan(&self) -> &str {
198        if self.color_enabled {
199            "\x1b[1;36m"
200        } else {
201            ""
202        }
203    }
204
205    fn yellow(&self) -> &str {
206        if self.color_enabled {
207            "\x1b[1;33m"
208        } else {
209            ""
210        }
211    }
212
213    fn green(&self) -> &str {
214        if self.color_enabled {
215            "\x1b[32m"
216        } else {
217            ""
218        }
219    }
220
221    fn italic_cyan(&self) -> &str {
222        if self.color_enabled {
223            "\x1b[3;36m"
224        } else {
225            ""
226        }
227    }
228
229    fn dim(&self) -> &str {
230        if self.color_enabled {
231            "\x1b[2;37m"
232        } else {
233            ""
234        }
235    }
236
237    fn reset(&self) -> &str {
238        if self.color_enabled {
239            "\x1b[0m"
240        } else {
241            ""
242        }
243    }
244
245    // ========================================================================
246    // Topic Content Generators
247    // ========================================================================
248
249    fn generate_basics_topic(color_enabled: bool) -> String {
250        let help = Self { topics: HashMap::new(), color_enabled };
251        let mut output = String::new();
252
253        output.push_str(&help.header("Getting Started with Oxur"));
254        output.push('\n');
255
256        output.push_str(&help.section("OVERVIEW"));
257        output.push_str("Oxur is a Lisp dialect that compiles to Rust. The REPL provides an\n");
258        output.push_str("interactive environment for evaluating Oxur expressions and seeing\n");
259        output.push_str("immediate results.\n\n");
260
261        output.push_str(&help.section("FIRST STEPS"));
262        output.push_str("Start by trying simple arithmetic:\n\n");
263        output.push_str(&help.example("  (+ 1 2)", Some("3")));
264        output.push_str(&help.example("  (* 6 7)", Some("42")));
265        output.push_str(&help.example("  (- 100 25)", Some("75")));
266        output.push('\n');
267
268        output.push_str("You can nest expressions:\n\n");
269        output.push_str(&help.example("  (+ (* 2 3) (/ 10 2))", Some("11")));
270        output.push('\n');
271
272        output.push_str(&help.section("DEFINING FUNCTIONS"));
273        output.push_str("Define functions with ");
274        output.push_str(&help.cmd_text("deffn"));
275        output.push_str(":\n\n");
276        output.push_str(&help.example("  (deffn square [x] (* x x))", None));
277        output.push_str(&help.example("  (square 5)", Some("25")));
278        output.push('\n');
279
280        output.push_str(&help.section("VARIABLES"));
281        output.push_str("Bind values to names with ");
282        output.push_str(&help.cmd_text("let"));
283        output.push_str(":\n\n");
284        output.push_str(&help.example("  (let x 42)", None));
285        output.push_str(&help.example("  (+ x 8)", Some("50")));
286        output.push('\n');
287
288        output.push_str(&help.note("💡 TIP: Press ↑ to recall previous commands from history"));
289        output.push('\n');
290
291        output.push_str("See also: ");
292        output.push_str(&help.cmd_text("(help evaluation)"));
293        output.push_str(", ");
294        output.push_str(&help.cmd_text("(help modes)"));
295        output.push('\n');
296
297        output
298    }
299
300    fn generate_evaluation_topic(color_enabled: bool) -> String {
301        let help = Self { topics: HashMap::new(), color_enabled };
302        let mut output = String::new();
303
304        output.push_str(&help.header("Evaluation System"));
305        output.push('\n');
306
307        output.push_str(&help.section("OVERVIEW"));
308        output
309            .push_str("Oxur evaluates Lisp expressions by compiling them to Rust and executing\n");
310        output.push_str(
311            "the generated code. To maximize performance, it uses a three-tier system.\n\n",
312        );
313
314        output.push_str(&help.section("THREE-TIER EXECUTION"));
315        output.push('\n');
316
317        output.push_str(&help.cmd_text("Tier 1: Calculator (~1ms)"));
318        output.push('\n');
319        output.push_str(&help.divider());
320        output.push_str("Simple arithmetic and basic operations are evaluated directly\n");
321        output.push_str("without compilation. This is the fastest path.\n\n");
322        output.push_str("Examples:\n");
323        output.push_str(&help.example("  (+ 1 2)", Some("Direct calculation")));
324        output.push_str(&help.example("  (* 10 20)", Some("Direct calculation")));
325        output.push('\n');
326
327        output.push_str(&help.cmd_text("Tier 2: Cached JIT (~1-5ms)"));
328        output.push('\n');
329        output.push_str(&help.divider());
330        output.push_str("Previously compiled expressions are cached in memory. When you\n");
331        output.push_str("evaluate the same expression again, the cached version is used.\n\n");
332        output.push_str("Example:\n");
333        output
334            .push_str(&help.example("  (deffn square [x] (* x x))", Some("Compiles once (~50ms)")));
335        output.push_str(&help.example("  (square 5)", Some("Uses cache (~2ms)")));
336        output.push_str(&help.example("  (square 10)", Some("Uses cache (~2ms)")));
337        output.push('\n');
338
339        output.push_str(&help.cmd_text("Tier 3: Fresh JIT (~50-300ms)"));
340        output.push('\n');
341        output.push_str(&help.divider());
342        output.push_str("New expressions are compiled on-the-fly. The first evaluation\n");
343        output.push_str("is slower but subsequent calls use the cache.\n\n");
344
345        output.push_str(
346            &help.note("⚡ TIP: The REPL shows you which tier was used for each evaluation"),
347        );
348        output.push('\n');
349
350        output.push_str("See also: ");
351        output.push_str(&help.cmd_text("(help performance)"));
352        output.push('\n');
353
354        output
355    }
356
357    fn generate_keyboard_topic(color_enabled: bool) -> String {
358        let help = Self { topics: HashMap::new(), color_enabled };
359        let mut output = String::new();
360
361        output.push_str(&help.header("Keyboard Shortcuts"));
362        output.push('\n');
363
364        output.push_str(&help.section("NAVIGATION"));
365        output.push_str(&help.command("←  →", "Move cursor left/right by character"));
366        output.push_str(&help.command("Ctrl-A", "Move to start of line"));
367        output.push_str(&help.command("Ctrl-E", "Move to end of line"));
368        output.push_str(&help.command("Alt-B", "Move backward by word"));
369        output.push_str(&help.command("Alt-F", "Move forward by word"));
370        output.push('\n');
371
372        output.push_str(&help.section("EDITING"));
373        output.push_str(&help.command("Ctrl-K", "Kill text from cursor to end of line"));
374        output.push_str(&help.command("Ctrl-U", "Kill text from start to cursor"));
375        output.push_str(&help.command("Ctrl-W", "Kill word before cursor"));
376        output.push_str(&help.command("Alt-D", "Kill word after cursor"));
377        output.push_str(&help.command("Ctrl-Y", "Yank (paste) killed text"));
378        output.push_str(&help.command("Ctrl-_", "Undo last edit"));
379        output.push('\n');
380
381        output.push_str(&help.section("HISTORY"));
382        output.push_str(&help.command("↑  ↓", "Navigate command history"));
383        output.push_str(&help.command("Ctrl-R", "Search history (incremental search)"));
384        output.push_str(&help.command("Ctrl-G", "Cancel incremental search"));
385        output.push('\n');
386
387        output.push_str(&help.section("CONTROL"));
388        output.push_str(&help.command("Ctrl-C", "Cancel current input line"));
389        output.push_str(&help.command("Ctrl-D", "Exit REPL (if line is empty)"));
390        output.push_str(&help.command("Ctrl-L", "Clear screen"));
391        output.push_str(&help.command("Enter", "Submit current line for evaluation"));
392        output.push('\n');
393
394        output.push_str(&help.note("💡 TIP: History is saved between REPL sessions"));
395        output.push('\n');
396
397        output
398    }
399
400    fn generate_sessions_topic(color_enabled: bool) -> String {
401        let help = Self { topics: HashMap::new(), color_enabled };
402        let mut output = String::new();
403
404        output.push_str(&help.header("Session Management"));
405        output.push('\n');
406
407        output.push_str(&help.section("OVERVIEW"));
408        output.push_str("A session represents an isolated evaluation environment with its\n");
409        output.push_str("own state, variables, and compilation cache. Each REPL connection\n");
410        output.push_str("creates a new session automatically.\n\n");
411
412        output.push_str(&help.section("SESSION MODES"));
413        output.push_str("Sessions can operate in two modes:\n\n");
414
415        output.push_str(&help.cmd_text("Lisp Mode (default)"));
416        output.push('\n');
417        output.push_str(&help.divider());
418        output.push_str("Full Lisp syntax with reader macros and syntactic sugar.\n");
419        output.push_str("This is the standard interactive mode.\n\n");
420        output.push_str("Examples: ");
421        output.push_str(&help.cmd_text("(+ 1 2)"));
422        output.push_str(", ");
423        output.push_str(&help.cmd_text("[1 2 3]"));
424        output.push_str(", ");
425        output.push_str(&help.cmd_text("{:key \"value\"}"));
426        output.push_str("\n\n");
427
428        output.push_str(&help.cmd_text("Sexpr Mode"));
429        output.push('\n');
430        output.push_str(&help.divider());
431        output.push_str("Raw S-expressions in canonical form (what Lisp expands to).\n");
432        output.push_str("Used for debugging the compiler or working with the AST directly.\n\n");
433        output.push_str("Example: ");
434        output.push_str(&help.cmd_text("(Add (Lit 1) (Lit 2))"));
435        output.push_str("\n\n");
436
437        output.push_str(&help.section("MULTIPLE SESSIONS"));
438        output.push_str("You can create and manage multiple sessions within a single REPL.\n");
439        output.push_str("Each session has its own isolated evaluation environment.\n\n");
440
441        output.push_str(&help.section("SESSION COMMANDS"));
442        output.push_str(&help.command("(sessions)", "List all active sessions"));
443        output.push_str(&help.command("(current-session)", "Show current session ID"));
444        output.push_str(&help.command("(new-session)", "Create unnamed session and switch to it"));
445        output.push_str(
446            &help.command("(new-session \"name\")", "Create named session and switch to it"),
447        );
448        output.push_str(&help.command("(switch-session <id>)", "Switch to existing session by ID"));
449        output.push_str(
450            &help.command("(close-session)", "Close current session (must switch first)"),
451        );
452        output.push_str(&help.command("(close-session <id>)", "Close specific session by ID"));
453        output.push('\n');
454
455        output.push_str(&help.section("WORKFLOW EXAMPLE"));
456        output
457            .push_str(&help.example("  (current-session)", Some("Shows your current session ID")));
458        output.push_str(&help.example(
459            "  (new-session \"experiment\")",
460            Some("Creates and switches to new session"),
461        ));
462        output
463            .push_str(&help.example("  (deffn foo [] 42)", Some("Define function in new session")));
464        output.push_str(&help.example("  (sessions)", Some("List all sessions")));
465        output.push_str(
466            &help.example("  (switch-session session-abc123)", Some("Switch back to original")),
467        );
468        output.push_str(
469            &help.example("  (close-session experiment-xyz)", Some("Close the experiment session")),
470        );
471        output.push('\n');
472
473        output.push_str(&help.note("💡 TIP: Session state persists until you close it"));
474        output.push('\n');
475        output.push_str(
476            &help.note("⚠️  NOTE: Cannot close the currently active session - switch first"),
477        );
478        output.push('\n');
479
480        output.push_str("See also: ");
481        output.push_str(&help.cmd_text("(help modes)"));
482        output.push('\n');
483
484        output
485    }
486
487    fn generate_commands_topic(color_enabled: bool) -> String {
488        let help = Self { topics: HashMap::new(), color_enabled };
489        let mut output = String::new();
490
491        output.push_str(&help.header("Special Commands Reference"));
492        output.push('\n');
493
494        output.push_str(&help.section("REPL CONTROL"));
495        output.push_str(&help.command("(banner)", "Redisplay the welcome banner"));
496        output.push_str(&help.command("(clear)", "Clear the terminal screen"));
497        output.push_str(&help.command("(exit)", "Exit the REPL (alias for quit)"));
498        output.push_str(&help.command("(help)", "Show help overview"));
499        output.push_str(&help.command("(help <topic>)", "Show detailed help for a specific topic"));
500        output.push_str(
501            &help.command("(info)", "Show system information (versions, platform, etc.)"),
502        );
503        output.push_str(&help.command("(q)", "Exit the REPL (short alias for quit)"));
504        output.push_str(&help.command("(quit)", "Exit the REPL gracefully"));
505        output.push('\n');
506
507        output.push_str(&help.section("SESSION MANAGEMENT"));
508        output.push_str(&help.command("(sessions)", "List all active sessions with metadata"));
509        output.push_str(&help.command("(current-session)", "Display current session ID"));
510        output.push_str(&help.command("(new-session)", "Create new unnamed session"));
511        output.push_str(&help.command("(new-session \"name\")", "Create new named session"));
512        output.push_str(&help.command("(switch-session <id>)", "Switch to existing session"));
513        output.push_str(&help.command("(close-session)", "Close current session (switch first)"));
514        output.push_str(&help.command("(close-session <id>)", "Close specific session by ID"));
515        output.push('\n');
516
517        output.push_str(&help.section("STATISTICS"));
518        output.push_str(&help.command("(stats)", "Show session summary (tiers, cache hit rate)"));
519        output
520            .push_str(&help.command("(stats execution)", "Show detailed tier performance metrics"));
521        output.push_str(&help.command("(stats cache)", "Show cache hit/miss statistics"));
522        output.push_str(&help.command("(stats resources)", "Show memory, disk, and file usage"));
523        output.push_str(&help.command("(stats server)", "Show server metrics (connect mode only)"));
524        output.push_str(&help.command("(stats subprocess)", "Show subprocess lifecycle stats"));
525        output.push('\n');
526
527        output.push_str(&help.section("KEYBOARD SHORTCUTS"));
528        output.push_str(&help.command("Ctrl-C", "Cancel current input and start fresh"));
529        output.push_str(&help.command("Ctrl-D", "Exit REPL if line is empty"));
530        output.push_str(&help.command("Ctrl-L", "Clear the screen"));
531        output.push('\n');
532
533        output.push_str(&help.section("EVALUATION"));
534        output.push_str("Any expression not matching a special command is evaluated\n");
535        output.push_str("as Oxur code using the current session's evaluation mode.\n\n");
536
537        output
538            .push_str(&help.note(
539                "💡 TIP: Special commands are handled locally and don't require compilation",
540            ));
541        output.push('\n');
542
543        output.push_str("See also: ");
544        output.push_str(&help.cmd_text("(help keyboard)"));
545        output.push_str(", ");
546        output.push_str(&help.cmd_text("(help evaluation)"));
547        output.push_str(", ");
548        output.push_str(&help.cmd_text("(help stats)"));
549        output.push('\n');
550
551        output
552    }
553
554    fn generate_modes_topic(color_enabled: bool) -> String {
555        let help = Self { topics: HashMap::new(), color_enabled };
556        let mut output = String::new();
557
558        output.push_str(&help.header("Evaluation Modes"));
559        output.push('\n');
560
561        output.push_str(&help.section("OVERVIEW"));
562        output.push_str("Oxur supports two evaluation modes: Lisp (user-friendly) and\n");
563        output.push_str("Sexpr (canonical form). The mode is set when starting the REPL.\n\n");
564
565        output.push_str(&help.section("LISP MODE (DEFAULT)"));
566        output.push_str("Full Lisp syntax with reader macros, syntactic sugar, and\n");
567        output.push_str("familiar Lisp idioms. This is the recommended mode for\n");
568        output.push_str("interactive development.\n\n");
569
570        output.push_str("Features:\n");
571        output.push_str("  • Reader macros: ");
572        output.push_str(&help.cmd_text("'expr"));
573        output.push_str(" → ");
574        output.push_str(&help.cmd_text("(quote expr)"));
575        output.push('\n');
576        output.push_str("  • Vector literals: ");
577        output.push_str(&help.cmd_text("[1 2 3]"));
578        output.push('\n');
579        output.push_str("  • Map literals: ");
580        output.push_str(&help.cmd_text("{:key \"value\"}"));
581        output.push('\n');
582        output.push_str("  • Set literals: ");
583        output.push_str(&help.cmd_text("#{1 2 3}"));
584        output.push_str("\n\n");
585
586        output.push_str(&help.section("SEXPR MODE"));
587        output.push_str("Raw S-expressions in canonical form. This mode shows you\n");
588        output.push_str("exactly what the Lisp reader produces after expansion.\n\n");
589
590        output.push_str("Use cases:\n");
591        output.push_str("  • Debugging macro expansions\n");
592        output.push_str("  • Understanding compiler internals\n");
593        output.push_str("  • Working directly with the AST\n");
594        output.push_str("  • Testing parser/expander behavior\n\n");
595
596        output.push_str(&help.section("SWITCHING MODES"));
597        output.push_str("The mode is set when launching the REPL:\n\n");
598        output.push_str(&help.example("  oxur repl", Some("Starts in Lisp mode")));
599        output.push_str(&help.example("  oxur repl --mode sexpr", Some("Starts in Sexpr mode")));
600        output.push('\n');
601
602        output.push_str(&help.note("💡 TIP: Most users should use Lisp mode for interactive work"));
603        output.push('\n');
604
605        output.push_str("See also: ");
606        output.push_str(&help.cmd_text("(help sessions)"));
607        output.push('\n');
608
609        output
610    }
611
612    fn generate_performance_topic(color_enabled: bool) -> String {
613        let help = Self { topics: HashMap::new(), color_enabled };
614        let mut output = String::new();
615
616        output.push_str(&help.header("Performance and Optimization"));
617        output.push('\n');
618
619        output.push_str(&help.section("EXECUTION TIERS"));
620        output.push_str("Oxur uses a three-tier execution strategy to balance\n");
621        output.push_str("startup time, throughput, and interactive responsiveness.\n\n");
622
623        output.push_str(&help.cmd_text("Tier 1: Calculator (~1ms)"));
624        output.push('\n');
625        output.push_str(&help.divider());
626        output.push_str("Eligible expressions:\n");
627        output.push_str("  • Pure arithmetic: (+, -, *, /)\n");
628        output.push_str("  • Literal values only (no variables)\n");
629        output.push_str("  • Nested arithmetic operations\n\n");
630        output.push_str("Performance: Sub-millisecond, no compilation overhead\n\n");
631
632        output.push_str(&help.cmd_text("Tier 2: Cached JIT (~1-5ms)"));
633        output.push('\n');
634        output.push_str(&help.divider());
635        output.push_str("Caching strategy:\n");
636        output.push_str("  • Content-addressed: Same code → same cache key\n");
637        output.push_str("  • Session-local: Each session has its own cache\n");
638        output.push_str("  • Persistent: Cache survives across evaluations\n\n");
639        output.push_str("Performance: Near-instant for cached code\n\n");
640
641        output.push_str(&help.cmd_text("Tier 3: Fresh JIT (~50-300ms)"));
642        output.push('\n');
643        output.push_str(&help.divider());
644        output.push_str("Compilation pipeline:\n");
645        output.push_str("  1. Parse Oxur → AST\n");
646        output.push_str("  2. Expand macros → Core forms\n");
647        output.push_str("  3. Lower → Rust AST\n");
648        output.push_str("  4. Codegen → Rust source\n");
649        output.push_str("  5. Compile → Dynamic library\n");
650        output.push_str("  6. Load → Execute\n\n");
651        output.push_str("Performance: One-time cost, then cached\n\n");
652
653        output.push_str(&help.section("OPTIMIZATION TIPS"));
654        output.push_str("⚡ Reuse code: Repeated evaluations use cached compilation\n");
655        output.push_str("⚡ Simple math: Use calculator tier when possible\n");
656        output.push_str("⚡ Define functions: Compiled once, called many times\n");
657        output.push_str("⚡ Batch work: Define multiple functions, then use them\n\n");
658
659        output.push_str(
660            &help.note("💡 TIP: Watch the execution tier in output to understand performance"),
661        );
662        output.push('\n');
663
664        output.push_str("See also: ");
665        output.push_str(&help.cmd_text("(help evaluation)"));
666        output.push('\n');
667
668        output
669    }
670
671    fn generate_stats_topic(color_enabled: bool) -> String {
672        let help = Self { topics: HashMap::new(), color_enabled };
673        let mut output = String::new();
674
675        output.push_str(&help.header("Statistics and Instrumentation"));
676        output.push('\n');
677
678        output.push_str(&help.section("OVERVIEW"));
679        output.push_str("The REPL provides comprehensive statistics and instrumentation to help\n");
680        output.push_str("you understand performance, resource usage, and execution patterns.\n");
681        output.push_str("All statistics use styled tables for clear presentation.\n\n");
682
683        output.push_str(&help.section("AVAILABLE VIEWS"));
684        output.push('\n');
685
686        output.push_str(&help.cmd_text("(stats)"));
687        output.push_str(" - Session Summary\n");
688        output.push_str(&help.divider());
689        output.push_str("Shows a high-level overview of your REPL session:\n");
690        output.push_str("  • Total evaluations performed\n");
691        output.push_str("  • Cache hit rate (hits, misses, percentage)\n");
692        output.push_str("  • Execution tier summary with percentiles (p50, p95, p99)\n\n");
693        output.push_str("Use this view to get a quick snapshot of session performance.\n\n");
694
695        output.push_str(&help.cmd_text("(stats execution)"));
696        output.push_str(" - Detailed Tier Breakdown\n");
697        output.push_str(&help.divider());
698        output.push_str("Shows detailed performance metrics for each execution tier:\n");
699        output.push_str("  • Count: Number of evaluations at this tier\n");
700        output.push_str("  • Min: Fastest evaluation time\n");
701        output.push_str("  • p50 (median): Typical evaluation time\n");
702        output.push_str("  • p95: 95th percentile (outliers excluded)\n");
703        output.push_str("  • p99: 99th percentile (almost all samples)\n");
704        output.push_str("  • Max: Slowest evaluation time\n\n");
705        output.push_str("Use this view to understand tier-specific performance patterns.\n\n");
706
707        output.push_str(&help.cmd_text("(stats cache)"));
708        output.push_str(" - Cache Metrics\n");
709        output.push_str(&help.divider());
710        output.push_str("Shows caching effectiveness:\n");
711        output.push_str("  • Hits: Number of cache hits (fast path)\n");
712        output.push_str("  • Misses: Number of cache misses (compilation required)\n");
713        output.push_str("  • Hit Rate: Percentage of evaluations served from cache\n\n");
714        output.push_str("A high hit rate (>70%) indicates good code reuse patterns.\n\n");
715
716        output.push_str(&help.cmd_text("(stats resources)"));
717        output.push_str(" - Resource Usage\n");
718        output.push_str(&help.divider());
719        output.push_str("Shows system resource consumption:\n\n");
720        output.push_str("Memory:\n");
721        output.push_str("  • Process RSS: Resident memory (actual RAM used)\n");
722        output.push_str("  • Virtual Memory: Total address space\n");
723        output.push_str("  • Process ID: Operating system PID\n\n");
724        output.push_str("Session Directory:\n");
725        output.push_str("  • Location: Path to session temp directory\n");
726        output.push_str("  • Files: Count of compiled artifacts\n");
727        output.push_str("  • Disk Usage: Total space consumed\n\n");
728        output.push_str("Artifact Cache (Global):\n");
729        output.push_str("  • Entries: Number of cached artifacts\n");
730        output.push_str("  • Total Size: Disk space used by cache\n");
731        output.push_str("  • Oldest Entry: Age of oldest cached item\n");
732        output.push_str("  • Cache Directory: Path to global cache\n\n");
733
734        output.push_str(&help.cmd_text("(stats server)"));
735        output.push_str(" - Server Metrics (Connect Mode Only)\n");
736        output.push_str(&help.divider());
737        output.push_str("Shows server-wide metrics when connected to a remote server:\n\n");
738        output.push_str("Connections:\n");
739        output.push_str("  • Total Connections: All-time connection count\n");
740        output.push_str("  • Active Connections: Currently connected clients\n\n");
741        output.push_str("Sessions:\n");
742        output.push_str("  • Total Sessions: All-time session count\n");
743        output.push_str("  • Active Sessions: Currently running sessions\n\n");
744        output.push_str("Requests & Responses:\n");
745        output.push_str("  • Total Requests/Responses: Message counts\n");
746        output.push_str("  • Successful/Errors: Response status breakdown\n");
747        output.push_str("  • Success Rate: Percentage of successful operations\n\n");
748        output.push_str("Note: Only available in 'connect' mode (remote server).\n");
749        output.push_str("In interactive mode, use 'oxur repl serve' to run a server.\n\n");
750
751        output.push_str(&help.cmd_text("(stats subprocess)"));
752        output.push_str(" - Subprocess Lifecycle\n");
753        output.push_str(&help.divider());
754        output.push_str("Shows subprocess lifecycle metrics (for forked execution):\n\n");
755        output.push_str("Status:\n");
756        output.push_str("  • Status: Running or Stopped\n");
757        output.push_str("  • Uptime: How long the subprocess has been running\n");
758        output.push_str("  • Restart Count: Number of restarts since session start\n");
759        output.push_str("  • Last Restart Reason: Why the subprocess last restarted\n\n");
760        output.push_str("Restart reasons include:\n");
761        output.push_str("  • Clean shutdown: Normal exit (code 0)\n");
762        output.push_str("  • Error exit: Non-zero exit code\n");
763        output.push_str("  • Segfault: Memory access violation (SIGSEGV)\n");
764        output.push_str("  • Killed: Process killed (SIGKILL, possibly OOM)\n");
765        output.push_str("  • Aborted: Assertion failure (SIGABRT)\n");
766        output.push_str("  • User requested: Manual restart command\n\n");
767
768        output.push_str(&help.section("PERCENTILES EXPLAINED"));
769        output.push_str("Percentiles give you a better understanding of typical performance\n");
770        output.push_str("than simple averages:\n\n");
771        output.push_str("  • ");
772        output.push_str(&help.cmd_text("p50 (median)"));
773        output.push_str(": Half of evaluations are faster\n");
774        output.push_str("  • ");
775        output.push_str(&help.cmd_text("p95"));
776        output.push_str(": 95% of evaluations are faster (excludes slow outliers)\n");
777        output.push_str("  • ");
778        output.push_str(&help.cmd_text("p99"));
779        output.push_str(": 99% of evaluations are faster (includes most samples)\n\n");
780        output.push_str("If p50 and p95 are close, performance is consistent.\n");
781        output
782            .push_str("If p99 is much higher than p95, you have occasional slow evaluations.\n\n");
783
784        output.push_str(&help.section("UNDERSTANDING THE TIERS"));
785        output.push_str("The stats show performance across three execution tiers:\n\n");
786        output.push_str(&help.cmd_text("Calculator"));
787        output.push_str(": Simple arithmetic (~1ms)\n");
788        output.push_str("  Fast, no compilation, direct evaluation\n\n");
789        output.push_str(&help.cmd_text("Cached"));
790        output.push_str(": Previously compiled (~1-5ms)\n");
791        output.push_str("  Near-instant, uses cached compilation\n\n");
792        output.push_str(&help.cmd_text("JIT"));
793        output.push_str(": Fresh compilation (~50-300ms)\n");
794        output.push_str("  Slower first time, then cached\n\n");
795
796        output.push_str(&help.section("OPTIMIZATION TIPS"));
797        output.push_str("Use stats to guide optimization:\n\n");
798        output.push_str("⚡ Low cache hit rate? Reuse more code patterns\n");
799        output.push_str("⚡ High p99 on Calculator tier? Simplify expressions\n");
800        output.push_str("⚡ Many JIT evaluations? Define functions for reuse\n");
801        output.push_str("⚡ High memory usage? Consider restarting REPL session\n\n");
802
803        output.push_str(&help.note("💡 TIP: Track stats over time to see performance trends"));
804        output.push('\n');
805
806        output.push_str("See also: ");
807        output.push_str(&help.cmd_text("(help performance)"));
808        output.push_str(", ");
809        output.push_str(&help.cmd_text("(help evaluation)"));
810        output.push('\n');
811
812        output
813    }
814}
815
816#[cfg(test)]
817mod tests {
818    use super::*;
819
820    #[test]
821    fn test_help_system_creation() {
822        let help = HelpSystem::new(true);
823        assert_eq!(help.list_topics().len(), 8);
824    }
825
826    #[test]
827    fn test_overview_has_all_sections() {
828        let help = HelpSystem::new(false);
829        let overview = help.show_overview();
830        assert!(overview.contains("GETTING STARTED"));
831        assert!(overview.contains("CONTROL COMMANDS"));
832        assert!(overview.contains("KEYBOARD SHORTCUTS"));
833        assert!(overview.contains("EVALUATION"));
834        assert!(overview.contains("HELP TOPICS"));
835    }
836
837    #[test]
838    fn test_all_topics_available() {
839        let help = HelpSystem::new(false);
840        let topics = vec![
841            "basics",
842            "evaluation",
843            "keyboard",
844            "sessions",
845            "commands",
846            "modes",
847            "performance",
848            "stats",
849        ];
850        for topic in topics {
851            assert!(help.show_topic(topic).is_some(), "Topic {} should exist", topic);
852        }
853    }
854
855    #[test]
856    fn test_color_disabled() {
857        let help = HelpSystem::new(false);
858        let overview = help.show_overview();
859        assert!(!overview.contains("\x1b["), "Should not contain ANSI codes");
860    }
861
862    #[test]
863    fn test_color_enabled() {
864        let help = HelpSystem::new(true);
865        let overview = help.show_overview();
866        assert!(overview.contains("\x1b["), "Should contain ANSI codes");
867    }
868
869    #[test]
870    fn test_unknown_topic() {
871        let help = HelpSystem::new(false);
872        assert!(help.show_topic("nonexistent").is_none());
873    }
874
875    #[test]
876    fn test_topics_list_is_sorted() {
877        let help = HelpSystem::new(false);
878        let topics = help.list_topics();
879        let mut sorted_topics = topics.clone();
880        sorted_topics.sort_unstable();
881        assert_eq!(topics, sorted_topics);
882    }
883
884    #[test]
885    fn test_each_topic_has_header() {
886        let help = HelpSystem::new(false);
887        for topic in help.list_topics() {
888            let content = help.show_topic(topic).unwrap();
889            assert!(content.contains("╭─"), "Topic {} should have header", topic);
890            assert!(content.contains("╰─"), "Topic {} should have header", topic);
891        }
892    }
893
894    #[test]
895    fn test_overview_mentions_all_topics() {
896        let help = HelpSystem::new(false);
897        let overview = help.show_overview();
898        for topic in help.list_topics() {
899            assert!(overview.contains(topic), "Overview should mention topic {}", topic);
900        }
901    }
902}