Skip to main content

logicaffeine_web/ui/components/
repl_output.rs

1//! REPL output panel for Code mode.
2//!
3//! Displays command history and execution results in a terminal-like interface.
4//! Shows success output in green and errors in red.
5//!
6//! # Props
7//!
8//! - `lines` - List of executed commands and their results
9//! - `on_clear` - Callback to clear history
10
11use dioxus::prelude::*;
12use crate::ui::state::ReplLine;
13
14const REPL_OUTPUT_STYLE: &str = r#"
15.repl-output {
16    display: flex;
17    flex-direction: column;
18    height: 100%;
19    min-height: 0;
20    background: #0f1419;
21    font-family: 'SF Mono', 'Fira Code', monospace;
22}
23
24.repl-output-header {
25    padding: 12px 16px;
26    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
27    background: rgba(255, 255, 255, 0.02);
28    display: flex;
29    justify-content: space-between;
30    align-items: center;
31}
32
33.repl-output-title {
34    font-size: 14px;
35    font-weight: 600;
36    color: rgba(255, 255, 255, 0.9);
37}
38
39.repl-clear-btn {
40    padding: 4px 10px;
41    border: 1px solid rgba(255, 255, 255, 0.1);
42    background: transparent;
43    color: rgba(255, 255, 255, 0.5);
44    font-size: 12px;
45    border-radius: 4px;
46    cursor: pointer;
47    transition: all 0.15s ease;
48}
49
50.repl-clear-btn:hover {
51    border-color: rgba(255, 255, 255, 0.2);
52    color: rgba(255, 255, 255, 0.8);
53}
54
55.repl-history {
56    flex: 1;
57    min-height: 0;
58    overflow: auto;
59    padding: 12px;
60}
61
62.repl-line {
63    margin-bottom: 16px;
64}
65
66.repl-input-line {
67    display: flex;
68    gap: 8px;
69    color: rgba(255, 255, 255, 0.6);
70    font-size: 13px;
71    line-height: 1.5;
72}
73
74.repl-prompt {
75    color: #667eea;
76    user-select: none;
77    flex-shrink: 0;
78}
79
80.repl-input-text {
81    color: rgba(255, 255, 255, 0.9);
82    white-space: pre-wrap;
83    word-break: break-word;
84}
85
86.repl-output-line {
87    padding-left: 24px;
88    margin-top: 4px;
89    font-size: 13px;
90    line-height: 1.5;
91    white-space: pre-wrap;
92    word-break: break-word;
93}
94
95.repl-output-line.success {
96    color: #4ade80;
97}
98
99.repl-output-line.error {
100    color: #e06c75;
101}
102
103.repl-empty {
104    display: flex;
105    flex-direction: column;
106    align-items: center;
107    justify-content: center;
108    height: 100%;
109    color: rgba(255, 255, 255, 0.4);
110    text-align: center;
111    padding: 20px;
112}
113
114.repl-empty .hint {
115    font-size: 13px;
116    margin-top: 8px;
117}
118
119.repl-empty .commands {
120    margin-top: 16px;
121    font-size: 12px;
122    color: rgba(255, 255, 255, 0.3);
123}
124
125.repl-empty .commands code {
126    background: rgba(255, 255, 255, 0.05);
127    padding: 2px 6px;
128    border-radius: 4px;
129    margin: 0 2px;
130}
131"#;
132
133#[component]
134pub fn ReplOutput(
135    lines: Vec<ReplLine>,
136    on_clear: EventHandler<()>,
137) -> Element {
138    rsx! {
139        style { "{REPL_OUTPUT_STYLE}" }
140
141        div { class: "repl-output",
142            // Header
143            div { class: "repl-output-header",
144                span { class: "repl-output-title", "Output" }
145                if !lines.is_empty() {
146                    button {
147                        class: "repl-clear-btn",
148                        onclick: move |_| on_clear.call(()),
149                        "Clear"
150                    }
151                }
152            }
153
154            // History
155            div { class: "repl-history",
156                if lines.is_empty() {
157                    div { class: "repl-empty",
158                        div { "No output yet" }
159                        div { class: "hint", "Execute code to see results here" }
160                        div { class: "commands",
161                            "Commands: "
162                            code { "Definition" }
163                            code { "Check" }
164                            code { "Eval" }
165                            code { "Inductive" }
166                        }
167                    }
168                } else {
169                    for (i, line) in lines.iter().enumerate() {
170                        div { key: "{i}", class: "repl-line",
171                            // Input line
172                            div { class: "repl-input-line",
173                                span { class: "repl-prompt", ">" }
174                                span { class: "repl-input-text", "{line.input}" }
175                            }
176
177                            // Output line
178                            match &line.output {
179                                Ok(output) if !output.is_empty() => rsx! {
180                                    div { class: "repl-output-line success", "{output}" }
181                                },
182                                Err(error) => rsx! {
183                                    div { class: "repl-output-line error", "{error}" }
184                                },
185                                _ => rsx! {}
186                            }
187                        }
188                    }
189                }
190            }
191        }
192    }
193}