1pub use crate::debugger::{
7 Breakpoint, BreakpointLocation, DebugCommand, DebuggerError, EnhancedError, ExecutionState,
8 InteractiveDebugger, StackFrame, VariableInfo,
9};
10pub use crate::dev_server::{
11 DevServer, DevServerConfig, DevServerError, FileChangeEvent, FileChangeType, HotReloadMessage,
12 HotReloadMessageType, MockBrowserClient, MockFileWatcher,
13};
14
15use crate::chart::MarkType;
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use std::path::{Path, PathBuf};
19use std::sync::{Arc, Mutex};
20use std::time::Instant;
21
22#[derive(Debug, thiserror::Error)]
24pub enum DevToolsError {
25 #[error("Dev server error: {0}")]
26 DevServerError(#[from] DevServerError),
27
28 #[error("Debugger error: {0}")]
29 DebuggerError(#[from] DebuggerError),
30
31 #[error("Code generation failed: {0}")]
32 CodeGenerationError(String),
33
34 #[error("Profiling error: {0}")]
35 ProfilingError(String),
36
37 #[error("Linting error: {0}")]
38 LintingError(String),
39
40 #[error("Template error: {0}")]
41 TemplateError(String),
42}
43
44impl From<String> for DevToolsError {
45 fn from(err: String) -> Self {
46 DevToolsError::LintingError(err)
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ProfileData {
53 pub function_name: String,
54 pub duration_ms: f64,
55 pub memory_usage_mb: f64,
56 pub cpu_usage_percent: f64,
57 pub call_count: u64,
58 pub children: Vec<ProfileData>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct CodeTemplate {
64 pub name: String,
65 pub description: String,
66 pub language: String,
67 pub template: String,
68 pub placeholders: Vec<TemplatePlaceholder>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct TemplatePlaceholder {
74 pub name: String,
75 pub description: String,
76 pub default_value: Option<String>,
77 pub required: bool,
78}
79
80#[derive(Debug, Clone)]
82pub struct LiveReloadConfig {
83 pub rust_files: bool,
84 pub css_files: bool,
85 pub js_files: bool,
86 pub html_files: bool,
87 pub asset_files: bool,
88 pub custom_extensions: Vec<String>,
89}
90
91impl Default for LiveReloadConfig {
92 fn default() -> Self {
93 Self {
94 rust_files: true,
95 css_files: true,
96 js_files: true,
97 html_files: true,
98 asset_files: true,
99 custom_extensions: vec!["toml".to_string(), "json".to_string()],
100 }
101 }
102}
103
104pub struct HeliosDev {
106 dev_server: Option<DevServer>,
107 debugger: InteractiveDebugger,
108 profiler: PerformanceProfiler,
109 code_generator: CodeGenerator,
110 linter: CodeLinter,
111 project_root: PathBuf,
112 config: DevToolsConfig,
113}
114
115#[derive(Debug, Clone)]
117pub struct DevToolsConfig {
118 pub dev_server_port: u16,
119 pub enable_hot_reload: bool,
120 pub enable_debugging: bool,
121 pub enable_profiling: bool,
122 pub enable_linting: bool,
123 pub live_reload: LiveReloadConfig,
124 pub auto_save: bool,
125 pub show_performance_overlay: bool,
126}
127
128impl Default for DevToolsConfig {
129 fn default() -> Self {
130 Self {
131 dev_server_port: 3000,
132 enable_hot_reload: true,
133 enable_debugging: true,
134 enable_profiling: true,
135 enable_linting: true,
136 live_reload: LiveReloadConfig::default(),
137 auto_save: true,
138 show_performance_overlay: false,
139 }
140 }
141}
142
143impl HeliosDev {
144 pub fn new<P: AsRef<Path>>(project_root: P) -> Self {
146 let project_root = project_root.as_ref().to_path_buf();
147 let config = DevToolsConfig::default();
148
149 Self {
150 dev_server: None,
151 debugger: InteractiveDebugger::new(),
152 profiler: PerformanceProfiler::new(),
153 code_generator: CodeGenerator::new(),
154 linter: CodeLinter::new(),
155 project_root,
156 config,
157 }
158 }
159
160 pub async fn start(&mut self) -> Result<(), DevToolsError> {
162 println!("🚀 Starting Helios Development Environment...");
163
164 if self.config.enable_hot_reload {
166 let mut server = DevServer::new(&self.project_root, self.config.dev_server_port);
167 server.start_with_websockets().await?;
168 self.dev_server = Some(server);
169 println!("✅ Development server started");
170 }
171
172 if self.config.enable_debugging {
174 let session_id = self.debugger.start_session("main")?;
175 println!("✅ Debug session started: {}", session_id);
176 }
177
178 if self.config.enable_profiling {
180 self.profiler.start_profiling()?;
181 println!("✅ Performance profiler started");
182 }
183
184 if self.config.enable_linting {
186 self.linter.initialize(&self.project_root).await?;
187 println!("✅ Code linter initialized");
188 }
189
190 println!("🎉 Development environment ready!");
191 Ok(())
192 }
193
194 pub fn stop(&mut self) {
196 if let Some(server) = &mut self.dev_server {
197 server.stop();
198 }
199
200 self.profiler.stop_profiling();
201 println!("🛑 Development environment stopped");
202 }
203
204 pub fn generate_chart_component(
206 &self,
207 name: &str,
208 chart_type: MarkType,
209 options: GenerationOptions,
210 ) -> Result<String, DevToolsError> {
211 self.code_generator
212 .generate_chart_component(name, chart_type, options)
213 .map_err(DevToolsError::CodeGenerationError)
214 }
215
216 pub fn add_breakpoint(
218 &mut self,
219 location: BreakpointLocation,
220 condition: Option<String>,
221 ) -> Result<String, DevToolsError> {
222 Ok(self.debugger.add_breakpoint(location, condition)?)
223 }
224
225 pub fn profile_operation(&self, name: &str) -> ProfileScope {
227 self.profiler.start_operation(name)
228 }
229
230 pub fn get_profile_data(&self) -> Vec<ProfileData> {
232 self.profiler.get_profile_data()
233 }
234
235 pub async fn lint_project(&self) -> Result<Vec<LintResult>, DevToolsError> {
237 self.linter
238 .lint_project(&self.project_root)
239 .await
240 .map_err(DevToolsError::LintingError)
241 }
242
243 pub fn watch_files(&self) -> MockFileWatcher {
245 if let Some(server) = &self.dev_server {
246 server.file_watcher()
247 } else {
248 use tokio::sync::broadcast;
250 let (_, rx) = broadcast::channel(100);
251 MockFileWatcher::new(rx)
252 }
253 }
254
255 pub async fn create_project_from_template(
257 &self,
258 template_name: &str,
259 project_name: &str,
260 output_dir: &Path,
261 ) -> Result<(), DevToolsError> {
262 self.code_generator
263 .create_project_from_template(template_name, project_name, output_dir)
264 .await
265 .map_err(DevToolsError::TemplateError)
266 }
267}
268
269pub struct PerformanceProfiler {
271 active_profiles: Arc<Mutex<HashMap<String, ProfileScope>>>,
272 completed_profiles: Arc<Mutex<Vec<ProfileData>>>,
273 enabled: bool,
274}
275
276impl PerformanceProfiler {
277 fn new() -> Self {
278 Self {
279 active_profiles: Arc::new(Mutex::new(HashMap::new())),
280 completed_profiles: Arc::new(Mutex::new(Vec::new())),
281 enabled: false,
282 }
283 }
284
285 fn start_profiling(&mut self) -> Result<(), DevToolsError> {
286 self.enabled = true;
287 Ok(())
288 }
289
290 fn stop_profiling(&mut self) {
291 self.enabled = false;
292 }
293
294 fn start_operation(&self, name: &str) -> ProfileScope {
295 if !self.enabled {
296 return ProfileScope::disabled();
297 }
298
299 let scope = ProfileScope::new(name);
300 let mut profiles = self.active_profiles.lock().unwrap();
301 profiles.insert(name.to_string(), scope.clone());
302 scope
303 }
304
305 fn get_profile_data(&self) -> Vec<ProfileData> {
306 self.completed_profiles.lock().unwrap().clone()
307 }
308}
309
310#[derive(Clone)]
312pub struct ProfileScope {
313 name: String,
314 start_time: Instant,
315 enabled: bool,
316}
317
318impl ProfileScope {
319 fn new(name: &str) -> Self {
320 Self {
321 name: name.to_string(),
322 start_time: Instant::now(),
323 enabled: true,
324 }
325 }
326
327 fn disabled() -> Self {
328 Self {
329 name: String::new(),
330 start_time: Instant::now(),
331 enabled: false,
332 }
333 }
334
335 pub fn finish(self) -> ProfileData {
336 if !self.enabled {
337 return ProfileData {
338 function_name: self.name,
339 duration_ms: 0.0,
340 memory_usage_mb: 0.0,
341 cpu_usage_percent: 0.0,
342 call_count: 0,
343 children: Vec::new(),
344 };
345 }
346
347 let duration = self.start_time.elapsed();
348 ProfileData {
349 function_name: self.name,
350 duration_ms: duration.as_secs_f64() * 1000.0,
351 memory_usage_mb: 25.0, cpu_usage_percent: 15.0, call_count: 1,
354 children: Vec::new(),
355 }
356 }
357}
358
359pub struct CodeGenerator {
361 templates: HashMap<String, CodeTemplate>,
362}
363
364#[derive(Debug, Clone)]
366pub struct GenerationOptions {
367 pub include_typescript: bool,
368 pub include_tests: bool,
369 pub include_docs: bool,
370 pub framework: String,
371}
372
373impl Default for GenerationOptions {
374 fn default() -> Self {
375 Self {
376 include_typescript: true,
377 include_tests: true,
378 include_docs: true,
379 framework: "leptos".to_string(),
380 }
381 }
382}
383
384impl CodeGenerator {
385 fn new() -> Self {
386 let mut generator = Self {
387 templates: HashMap::new(),
388 };
389 generator.initialize_templates();
390 generator
391 }
392
393 fn initialize_templates(&mut self) {
394 let chart_template = CodeTemplate {
396 name: "chart_component".to_string(),
397 description: "Generate a new chart component".to_string(),
398 language: "rust".to_string(),
399 template: r#"//! {{component_name}} Chart Component
400//!
401//! {{description}}
402
403use helios_core::prelude::*;
404use leptos::prelude::*;
405use serde::{Deserialize, Serialize};
406
407/// {{component_name}} component properties
408#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
409pub struct {{component_name}}Props {
410 pub data: DataFrame,
411 pub width: Option<u32>,
412 pub height: Option<u32>,
413 pub title: Option<String>,
414 {{#if include_typescript}}
415 pub on_click: Option<Callback<ChartEvent>>,
416 {{/if}}
417}
418
419/// {{component_name}} chart component
420#[component]
421pub fn {{component_name}}(props: {{component_name}}Props) -> impl IntoView {
422 let chart_spec = ChartSpec::new()
423 .mark({{mark_type}})
424 .width(props.width.unwrap_or(400))
425 .height(props.height.unwrap_or(300));
426
427 {{#if include_typescript}}
428 let handle_click = move |event: ChartEvent| {
429 if let Some(callback) = &props.on_click {
430 callback.call(event);
431 }
432 };
433 {{/if}}
434
435 view! {
436 <div class="helios-{{component_name_lower}}-chart">
437 {{#if title}}
438 <h3 class="chart-title">{props.title}</h3>
439 {{/if}}
440 <HeliosChart
441 spec={chart_spec}
442 data={props.data}
443 {{#if include_typescript}}
444 on:click=handle_click
445 {{/if}}
446 />
447 </div>
448 }
449}
450
451{{#if include_tests}}
452#[cfg(test)]
453mod tests {
454 use super::*;
455 use polars::prelude::*;
456
457 #[test]
458 fn test_{{component_name_lower}}_creation() {
459 let data = df! {
460 "x" => [1, 2, 3, 4],
461 "y" => [10, 20, 15, 25],
462 }.unwrap();
463
464 let props = {{component_name}}Props {
465 data,
466 width: Some(800),
467 height: Some(600),
468 title: Some("Test Chart".to_string()),
469 {{#if include_typescript}}
470 on_click: None,
471 {{/if}}
472 };
473
474 // Component creation should not panic
475 let _component = {{component_name}}(props);
476 }
477}
478{{/if}}
479"#
480 .to_string(),
481 placeholders: vec![
482 TemplatePlaceholder {
483 name: "component_name".to_string(),
484 description: "Name of the chart component".to_string(),
485 default_value: Some("MyChart".to_string()),
486 required: true,
487 },
488 TemplatePlaceholder {
489 name: "description".to_string(),
490 description: "Component description".to_string(),
491 default_value: Some("A custom chart component".to_string()),
492 required: false,
493 },
494 TemplatePlaceholder {
495 name: "mark_type".to_string(),
496 description: "Chart mark type".to_string(),
497 default_value: Some("MarkType::Line".to_string()),
498 required: true,
499 },
500 ],
501 };
502
503 self.templates
504 .insert("chart_component".to_string(), chart_template);
505 }
506
507 fn generate_chart_component(
508 &self,
509 name: &str,
510 chart_type: MarkType,
511 options: GenerationOptions,
512 ) -> Result<String, String> {
513 let template = self
514 .templates
515 .get("chart_component")
516 .ok_or("Chart component template not found")?;
517
518 let mark_type_str = match chart_type {
519 MarkType::Line { .. } => {
520 "MarkType::Line { interpolate: None, stroke_width: None, stroke_dash: None }"
521 }
522 MarkType::Bar { .. } => "MarkType::Bar { width: None, corner_radius: None }",
523 MarkType::Point { .. } => "MarkType::Point { size: None, shape: None, opacity: None }",
524 MarkType::Area { .. } => "MarkType::Area { interpolate: None, opacity: None }",
525 _ => "MarkType::Line { interpolate: None, stroke_width: None, stroke_dash: None }",
526 };
527
528 let mut code = template.template.clone();
529 code = code.replace("{{component_name}}", name);
530 code = code.replace("{{component_name_lower}}", &name.to_lowercase());
531 code = code.replace("{{mark_type}}", mark_type_str);
532 code = code.replace("{{description}}", &format!("A {} chart component", name));
533
534 if options.include_typescript {
536 code = code.replace("{{#if include_typescript}}", "");
537 code = code.replace("{{/if}}", "");
538 } else {
539 let lines: Vec<&str> = code.lines().collect();
541 let mut filtered_lines = Vec::new();
542 let mut skip = false;
543
544 for line in lines {
545 if line.contains("{{#if include_typescript}}") {
546 skip = true;
547 continue;
548 }
549 if line.contains("{{/if}}") && skip {
550 skip = false;
551 continue;
552 }
553 if !skip {
554 filtered_lines.push(line);
555 }
556 }
557 code = filtered_lines.join("\n");
558 }
559
560 if options.include_tests {
561 code = code.replace("{{#if include_tests}}", "");
562 } else {
563 let lines: Vec<&str> = code.lines().collect();
565 let mut filtered_lines = Vec::new();
566 let mut skip = false;
567
568 for line in lines {
569 if line.contains("{{#if include_tests}}") {
570 skip = true;
571 continue;
572 }
573 if line.contains("{{/if}}") && skip {
574 skip = false;
575 continue;
576 }
577 if !skip {
578 filtered_lines.push(line);
579 }
580 }
581 code = filtered_lines.join("\n");
582 }
583
584 code = code.replace("{{#if title}}", "");
586 code = code.replace("{{/if}}", "");
587
588 Ok(code)
589 }
590
591 async fn create_project_from_template(
592 &self,
593 _template_name: &str,
594 _project_name: &str,
595 _output_dir: &Path,
596 ) -> Result<(), String> {
597 Ok(())
599 }
600}
601
602pub struct CodeLinter {
604 rules: Vec<LintRule>,
605}
606
607#[derive(Debug, Clone)]
609pub struct LintRule {
610 pub name: String,
611 pub description: String,
612 pub severity: LintSeverity,
613 pub enabled: bool,
614}
615
616#[derive(Debug, Clone, PartialEq)]
618pub enum LintSeverity {
619 Error,
620 Warning,
621 Info,
622 Hint,
623}
624
625#[derive(Debug, Clone)]
627pub struct LintResult {
628 pub file_path: String,
629 pub rule_name: String,
630 pub message: String,
631 pub severity: LintSeverity,
632 pub line: Option<u32>,
633 pub column: Option<u32>,
634}
635
636impl CodeLinter {
637 fn new() -> Self {
638 Self {
639 rules: Self::default_rules(),
640 }
641 }
642
643 fn default_rules() -> Vec<LintRule> {
644 vec![
645 LintRule {
646 name: "unused_imports".to_string(),
647 description: "Detect unused imports".to_string(),
648 severity: LintSeverity::Warning,
649 enabled: true,
650 },
651 LintRule {
652 name: "missing_docs".to_string(),
653 description: "Public items should have documentation".to_string(),
654 severity: LintSeverity::Info,
655 enabled: true,
656 },
657 LintRule {
658 name: "performance_warning".to_string(),
659 description: "Detect potential performance issues".to_string(),
660 severity: LintSeverity::Hint,
661 enabled: true,
662 },
663 ]
664 }
665
666 async fn initialize(&mut self, _project_root: &Path) -> Result<(), String> {
667 Ok(())
669 }
670
671 async fn lint_project(&self, project_root: &Path) -> Result<Vec<LintResult>, String> {
672 Ok(vec![
674 LintResult {
675 file_path: project_root
676 .join("src/main.rs")
677 .to_string_lossy()
678 .to_string(),
679 rule_name: "unused_imports".to_string(),
680 message: "Unused import `std::collections::HashMap`".to_string(),
681 severity: LintSeverity::Warning,
682 line: Some(3),
683 column: Some(5),
684 },
685 LintResult {
686 file_path: project_root
687 .join("src/chart.rs")
688 .to_string_lossy()
689 .to_string(),
690 rule_name: "missing_docs".to_string(),
691 message: "Public function `render` is missing documentation".to_string(),
692 severity: LintSeverity::Info,
693 line: Some(45),
694 column: Some(1),
695 },
696 ])
697 }
698}
699
700pub mod utils {
702 use super::*;
703
704 pub fn generate_component_boilerplate(name: &str) -> String {
706 format!(
707 r#"//! {} Component
708//!
709//! Generated by Helios Dev Tools
710
711use leptos::prelude::*;
712
713#[component]
714pub fn {}() -> impl IntoView {{
715 view! {{
716 <div class="helios-{}">
717 <p>"Hello from {}!"</p>
718 </div>
719 }}
720}}
721"#,
722 name,
723 name,
724 name.to_lowercase(),
725 name
726 )
727 }
728
729 pub async fn format_code(code: &str) -> Result<String, String> {
731 Ok(code.to_string())
733 }
734
735 pub async fn generate_docs(project_root: &Path) -> Result<String, String> {
737 Ok(format!(
739 "# Documentation for {}\n\nGenerated by Helios Dev Tools",
740 project_root.file_name().unwrap().to_string_lossy()
741 ))
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748 use tempfile::tempdir;
749
750 #[tokio::test]
751 async fn test_helios_dev_creation() {
752 let temp_dir = tempdir().unwrap();
753 let dev = HeliosDev::new(temp_dir.path());
754
755 assert_eq!(dev.project_root, temp_dir.path());
756 assert_eq!(dev.config.dev_server_port, 3000);
757 }
758
759 #[tokio::test]
760 async fn test_code_generation() {
761 let temp_dir = tempdir().unwrap();
762 let dev = HeliosDev::new(temp_dir.path());
763
764 let code = dev
765 .generate_chart_component(
766 "LineChart",
767 MarkType::Line {
768 interpolate: None,
769 stroke_width: None,
770 stroke_dash: None,
771 },
772 GenerationOptions::default(),
773 )
774 .unwrap();
775
776 assert!(code.contains("LineChart"));
777 assert!(code.contains("MarkType::Line"));
778 assert!(code.contains("#[component]"));
779 }
780
781 #[test]
782 fn test_performance_profiling() {
783 use std::time::Duration;
784 let mut profiler = PerformanceProfiler::new();
785 profiler.start_profiling().unwrap(); let scope = profiler.start_operation("test_function");
787
788 std::thread::sleep(Duration::from_millis(10));
790
791 let profile_data = scope.finish();
792 assert_eq!(profile_data.function_name, "test_function");
793 assert!(profile_data.duration_ms >= 10.0);
794 }
795
796 #[tokio::test]
797 async fn test_code_linting() {
798 let temp_dir = tempdir().unwrap();
799 let linter = CodeLinter::new();
800
801 let results = linter.lint_project(temp_dir.path()).await.unwrap();
802 assert!(!results.is_empty());
803 assert!(results.iter().any(|r| r.rule_name == "unused_imports"));
804 }
805
806 #[test]
807 fn test_utils_component_generation() {
808 let code = utils::generate_component_boilerplate("TestComponent");
809 assert!(code.contains("TestComponent"));
810 assert!(code.contains("#[component]"));
811 assert!(code.contains("Hello from TestComponent!"));
812 }
813}