1#![cfg_attr(docsrs, feature(doc_cfg))]
24
25pub mod logger;
26
27use async_trait::async_trait;
28use crossterm::{
29 cursor,
30 event::{
31 self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind,
32 KeyModifiers, poll,
33 },
34 execute,
35 terminal::{Clear, ClearType, disable_raw_mode, enable_raw_mode},
36};
37use futures::future::BoxFuture;
38use std::collections::HashMap;
39use std::io::{Stdout, Write, stdout};
40use std::time::Instant;
41use tokio::sync::mpsc;
42use tokio::task::JoinHandle;
43use unicode_width::UnicodeWidthChar;
44
45pub enum AppAction {
47 RegisterCommand(String, Box<dyn CommandHandler>),
49}
50
51impl std::fmt::Debug for AppAction {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 AppAction::RegisterCommand(name, _) => f
55 .debug_struct("RegisterCommand")
56 .field("name", name)
57 .field("handler", &"Box<dyn CommandHandler>")
58 .finish(),
59 }
60 }
61}
62
63type UnknownCommandHandler = Box<dyn Fn(&str) -> String + Send + Sync + 'static>;
65
66type AsyncUnknownCommandHandler =
68 Box<dyn Fn(&str) -> BoxFuture<'static, String> + Send + Sync + 'static>;
69
70#[derive(Debug)]
72pub struct CommandResult {
73 pub command: String,
74 pub output: String,
75}
76
77#[derive(Debug)]
79struct RunningCommand {
80 pub command: String,
81 pub handle: JoinHandle<String>,
82}
83
84pub trait CommandHandler: Send + Sync + 'static {
89 fn execute(&mut self, app: &mut TerminalApp, args: &[&str]) -> String;
100}
101
102#[async_trait]
107pub trait AsyncCommandHandler: Send + Sync + 'static {
108 async fn execute_async(&mut self, app: &mut TerminalApp, args: &[&str]) -> String;
119
120 fn box_clone(&self) -> Box<dyn AsyncCommandHandler>;
122}
123
124enum CommandHandlerType {
126 Sync(Box<dyn CommandHandler>),
127 Async(Box<dyn AsyncCommandHandler>),
128}
129
130impl<F> CommandHandler for F
135where
136 F: FnMut(&mut TerminalApp, &[&str]) -> String + Send + Sync + 'static,
137{
138 fn execute(&mut self, app: &mut TerminalApp, args: &[&str]) -> String {
139 self(app, args)
140 }
141}
142
143pub struct TerminalApp {
152 pub stdout_handle: Stdout,
153 pub command_history: Vec<String>,
154 pub current_input: String,
155 pub history_index: Option<usize>,
156 pub last_ctrl_c: Option<Instant>,
157 pub cursor_position: usize,
158 pub should_exit: bool,
159 commands: HashMap<String, CommandHandlerType>,
160 unknown_command_handler: Option<UnknownCommandHandler>,
161 async_unknown_command_handler: Option<AsyncUnknownCommandHandler>,
162 command_result_rx: Option<mpsc::UnboundedReceiver<CommandResult>>,
163 command_result_tx: Option<mpsc::UnboundedSender<CommandResult>>,
164 running_commands: Vec<RunningCommand>,
165 last_key_event: Option<KeyEvent>,
166 pub action_sender: Option<mpsc::UnboundedSender<AppAction>>,
167}
168
169impl Default for TerminalApp {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175impl TerminalApp {
176 fn remove_char_at(&mut self, index: usize) {
178 let mut chars: Vec<char> = self.current_input.chars().collect();
179 if index < chars.len() {
180 chars.remove(index);
181 self.current_input = chars.into_iter().collect();
182 }
183 }
184
185 pub fn new() -> Self {
187 let (tx, rx) = mpsc::unbounded_channel();
188 Self {
189 stdout_handle: stdout(),
190 command_history: Vec::new(),
191 current_input: String::new(),
192 history_index: None,
193 last_ctrl_c: None,
194 cursor_position: 0,
195 should_exit: false,
196 commands: HashMap::new(),
197 unknown_command_handler: None,
198 async_unknown_command_handler: None,
199 command_result_rx: Some(rx),
200 command_result_tx: Some(tx),
201 running_commands: Vec::new(),
202 last_key_event: None,
203 action_sender: None,
204 }
205 }
206
207 pub fn register_command<S: Into<String>>(&mut self, name: S, handler: Box<dyn CommandHandler>) {
214 self.commands
215 .insert(name.into(), CommandHandlerType::Sync(handler));
216 }
217
218 pub fn register_async_command<S: Into<String>>(
225 &mut self,
226 name: S,
227 handler: Box<dyn AsyncCommandHandler>,
228 ) {
229 self.commands
230 .insert(name.into(), CommandHandlerType::Async(handler));
231 }
232
233 pub(crate) fn set_action_sender(&mut self, sender: mpsc::UnboundedSender<AppAction>) {
239 self.action_sender = Some(sender);
240 }
241
242 pub fn set_unknown_command_handler<F>(&mut self, handler: F)
248 where
249 F: Fn(&str) -> String + Send + Sync + 'static,
250 {
251 self.unknown_command_handler = Some(Box::new(handler));
252 }
253
254 pub fn set_async_unknown_command_handler<F>(&mut self, handler: F)
260 where
261 F: Fn(&str) -> BoxFuture<'static, String> + Send + Sync + 'static,
262 {
263 self.async_unknown_command_handler = Some(Box::new(handler));
264 }
265
266 pub fn clear_unknown_command_handler(&mut self) {
268 self.unknown_command_handler = None;
269 self.async_unknown_command_handler = None;
270 }
271
272 pub async fn init_terminal(
282 &mut self,
283 startup_message: &str,
284 ) -> Result<(), Box<dyn std::error::Error>> {
285 self.setup_terminal()?;
286
287 if !startup_message.is_empty() {
288 self.print_startup_message(startup_message).await?;
289 }
290
291 Ok(())
292 }
293
294 fn setup_terminal(&mut self) -> Result<(), Box<dyn std::error::Error>> {
296 enable_raw_mode()?;
297 execute!(&mut self.stdout_handle, EnableMouseCapture, cursor::Hide)?;
298 self.stdout_handle.flush()?;
299 Ok(())
300 }
301
302 async fn print_startup_message(
303 &mut self,
304 message: &str,
305 ) -> Result<(), Box<dyn std::error::Error>> {
306 writeln!(self.stdout_handle, "{}", message)?;
307 self.stdout_handle.flush()?;
308 Ok(())
309 }
310
311 pub async fn process_event(
326 &mut self,
327 event: Event,
328 ) -> Result<bool, Box<dyn std::error::Error>> {
329 let mut should_quit = false;
330
331 if let Event::Key(key_event) = &event {
332 if key_event.kind == KeyEventKind::Release {
344 return Ok(should_quit);
345 }
346
347 if let Some(last_event) = &self.last_key_event {
348 if last_event.code == key_event.code
349 && last_event.modifiers == key_event.modifiers
350 && last_event.kind == key_event.kind
351 {
352 let is_control_key = match key_event.code {
353 KeyCode::Char('c') if key_event.modifiers == KeyModifiers::CONTROL => true,
354 KeyCode::Char('d') if key_event.modifiers == KeyModifiers::CONTROL => true,
355 _ => false,
356 };
357
358 if !is_control_key {
359 return Ok(should_quit);
360 }
361 }
362 }
363
364 match key_event.code {
365 KeyCode::Char('c') if key_event.modifiers == KeyModifiers::CONTROL => {
366 self.last_key_event = Some(*key_event);
367 }
368 KeyCode::Char('d') if key_event.modifiers == KeyModifiers::CONTROL => {
369 self.last_key_event = Some(*key_event);
370 }
371 _ => {}
372 }
373 }
374
375 if let Event::Key(KeyEvent {
376 code, modifiers, ..
377 }) = event
378 {
379 match code {
380 KeyCode::Char('d') if modifiers == KeyModifiers::CONTROL => {
381 should_quit = self.handle_ctrl_d().await?;
382 }
383 KeyCode::Char('c') if modifiers == KeyModifiers::CONTROL => {
384 let (quit, message) = self.handle_ctrl_c().await?;
385 should_quit = quit;
386 self.print_log_entry(&message);
387 }
388 KeyCode::Up => {
389 self.handle_up_key();
390 self.render_input_line()?;
391 }
392 KeyCode::Down => {
393 self.handle_down_key();
394 self.render_input_line()?;
395 }
396 KeyCode::Left => {
397 if self.cursor_position > 0 {
398 self.cursor_position -= 1;
399 self.render_input_line()?;
400 }
401 }
402 KeyCode::Right => {
403 if self.cursor_position < self.current_input.chars().count() {
404 self.cursor_position += 1;
405 self.render_input_line()?;
406 }
407 }
408 KeyCode::Enter => {
409 let should_exit = self.handle_enter_key("> ").await?;
410 if should_exit {
411 return Ok(true);
412 }
413 }
414 KeyCode::Char(c) => {
415 self.handle_char_input(c);
416 self.render_input_line()?;
417 }
418 KeyCode::Backspace => {
419 if self.cursor_position > 0 {
420 self.remove_char_at(self.cursor_position - 1);
421 self.cursor_position -= 1;
422 self.render_input_line()?;
423 }
424 }
425 _ => {}
426 }
427 }
428 Ok(should_quit)
429 }
430
431 pub async fn shutdown_terminal(
442 &mut self,
443 exit_message: &str,
444 ) -> Result<(), Box<dyn std::error::Error>> {
445 disable_raw_mode()?;
446 writeln!(self.stdout_handle, "{}", exit_message)?;
447 self.stdout_handle.flush()?;
448 Ok(())
449 }
450
451 pub async fn run(
466 &mut self,
467 startup_message: &str,
468 exit_message: &str,
469 ) -> Result<(), Box<dyn std::error::Error>> {
470 let (action_tx, mut action_rx) = mpsc::unbounded_channel::<AppAction>();
472 self.set_action_sender(action_tx.clone());
473
474 enable_raw_mode()?;
475 execute!(self.stdout_handle, EnableMouseCapture, cursor::Hide)?;
476
477 if !startup_message.is_empty() {
478 self.print_log_entry(startup_message);
479 }
480
481 loop {
482 while let Ok(action) = action_rx.try_recv() {
484 match action {
485 AppAction::RegisterCommand(name, handler) => {
486 self.register_command(name, handler);
487 }
488 }
489 }
490
491 self.check_running_commands().await?;
493
494 if let Some(ref mut rx) = self.command_result_rx {
496 if let Ok(result) = rx.try_recv() {
497 self.handle_command_result(result).await?;
498 }
499 }
500
501 tokio::select! {
503 _ = tokio::time::sleep(tokio::time::Duration::from_millis(50)) => {
504 if poll(std::time::Duration::from_millis(0))? {
506 if let Ok(event) = event::read() {
507 if self.process_event(event).await? {
508 break;
509 }
510 }
511 }
512 }
513 }
514
515 if self.should_exit {
516 break;
517 }
518 }
519
520 disable_raw_mode()?;
521 execute!(self.stdout_handle, DisableMouseCapture, cursor::Show)?;
522
523 if !exit_message.is_empty() {
524 println!("{}", exit_message);
525 }
526
527 Ok(())
528 }
529
530 pub fn clear_input_line(&mut self) {
532 let _ = execute!(
533 self.stdout_handle,
534 cursor::MoveToColumn(0),
535 Clear(ClearType::CurrentLine)
536 );
537 }
538
539 pub fn print_log_entry(&mut self, log_line: &str) {
552 self.clear_input_line();
553 let _ = writeln!(self.stdout_handle, "{}", log_line);
554 let _ = self.stdout_handle.flush();
555 let _ = self.render_input_line();
556 }
557
558 fn render_input_line(&mut self) -> Result<(), Box<dyn std::error::Error>> {
560 let result = (|| -> Result<(), Box<dyn std::error::Error>> {
561 execute!(self.stdout_handle, cursor::Hide)?;
562 self.clear_input_line();
563 execute!(
564 self.stdout_handle,
565 crossterm::style::Print("> "),
566 crossterm::style::Print(&self.current_input)
567 )?;
568 let visual_cursor_pos = 2 + self
569 .current_input
570 .chars()
571 .take(self.cursor_position)
572 .map(|c| c.width().unwrap_or(0))
573 .sum::<usize>();
574 execute!(
575 self.stdout_handle,
576 cursor::MoveToColumn(visual_cursor_pos as u16),
577 cursor::Show
578 )?;
579 self.stdout_handle.flush()?;
580 Ok(())
581 })();
582 if result.is_err() {
583 let _ = execute!(self.stdout_handle, cursor::Show);
584 }
585 result
586 }
587
588 pub async fn handle_ctrl_d(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
594 Ok(true)
595 }
596
597 pub async fn handle_ctrl_c(&mut self) -> Result<(bool, String), Box<dyn std::error::Error>> {
605 if !self.current_input.is_empty() {
606 self.current_input.clear();
607 self.cursor_position = 0;
608 self.last_ctrl_c = Some(Instant::now());
609 return Ok((
610 false,
611 get_info!(
612 "Input cleared. Press Ctrl+C again to exit.",
613 "Daemon Console"
614 ),
615 ));
616 }
617 if let Some(last_time) = self.last_ctrl_c
618 && last_time.elapsed().as_secs() < 5
619 {
620 return Ok((
621 true,
622 get_warn!("Exiting application. Goodbye!", "Daemon Console"),
623 ));
624 }
625 self.last_ctrl_c = Some(Instant::now());
626 Ok((
627 false,
628 get_info!("Press Ctrl+C again to exit.", "Daemon Console"),
629 ))
630 }
631
632 fn handle_up_key(&mut self) {
634 if self.command_history.is_empty() {
635 return;
636 }
637 let new_index = match self.history_index {
638 Some(idx) if idx > 0 => idx - 1,
639 Some(_) => return,
640 None => self.command_history.len() - 1,
641 };
642 self.history_index = Some(new_index);
643 self.current_input = self.command_history[new_index].clone();
644 self.cursor_position = self.current_input.chars().count();
645 }
646
647 fn handle_down_key(&mut self) {
649 let new_index = match self.history_index {
650 Some(idx) if idx < self.command_history.len() - 1 => idx + 1,
651 Some(_) => {
652 self.history_index = None;
653 self.current_input.clear();
654 self.cursor_position = 0;
655 return;
656 }
657 None => return,
658 };
659 self.history_index = Some(new_index);
660 self.current_input = self.command_history[new_index].clone();
661 self.cursor_position = self.current_input.chars().count();
662 }
663
664 pub async fn handle_enter_key(
675 &mut self,
676 input_prefix: &str,
677 ) -> Result<bool, Box<dyn std::error::Error>> {
678 if !self.current_input.trim().is_empty() {
679 self.command_history.push(self.current_input.clone());
680 self.clear_input_line();
681 writeln!(self.stdout_handle, "{}{}", input_prefix, self.current_input)?;
682 let input_copy = self.current_input.clone();
683 let command_output = self.execute_command(&input_copy).await;
684 if !command_output.is_empty() {
685 for line in command_output.lines() {
686 execute!(self.stdout_handle, cursor::MoveToColumn(0))?;
687 writeln!(self.stdout_handle, "{}", line.trim_start())?;
688 }
689 } else {
690 writeln!(self.stdout_handle)?;
691 }
692 self.current_input.clear();
693 self.cursor_position = 0;
694 self.history_index = None;
695 self.render_input_line()?;
696 } else {
697 self.clear_input_line();
698 self.render_input_line()?;
699 }
700 Ok(self.should_exit)
701 }
702
703 pub async fn execute_command(&mut self, command: &str) -> String {
716 let parts: Vec<&str> = command.split_whitespace().collect();
717 if parts.is_empty() {
718 return String::new();
719 }
720
721 let cmd_name = parts[0];
722 let args = &parts[1..];
723
724 if let Some(handler) = self.commands.get(cmd_name) {
725 match handler {
726 CommandHandlerType::Sync(_) => {
727 if let Some(CommandHandlerType::Sync(mut sync_handler)) =
729 self.commands.remove(cmd_name)
730 {
731 let result = sync_handler.execute(self, args);
732 self.commands
733 .insert(cmd_name.to_string(), CommandHandlerType::Sync(sync_handler));
734 result
735 } else {
736 get_error!("Internal error: sync handler not found", "CommandStatus")
737 }
738 }
739 CommandHandlerType::Async(async_handler) => {
740 let cloned_handler = async_handler.box_clone();
742 match self
743 .spawn_async_command(command.to_string(), cloned_handler)
744 .await
745 {
746 Ok(_) => {
747 get_info!(
748 &format!("Async command '{}' started in the background", cmd_name),
749 "CommandStatus"
750 )
751 }
752 Err(e) => {
753 get_error!(
754 &format!("Failed to spawn async command: {}", e),
755 "CommandStatus"
756 )
757 }
758 }
759 }
760 }
761 } else {
762 if let Some(ref handler) = self.async_unknown_command_handler {
763 handler(command).await
764 } else if let Some(ref handler) = self.unknown_command_handler {
765 handler(command)
766 } else {
767 get_warn!(
768 &format!("Command not found or registered: '{}'", command),
769 "CommandStatus"
770 )
771 }
772 }
773 }
774
775 fn handle_char_input(&mut self, c: char) {
777 let char_count = self.current_input.chars().count();
778
779 if self.cursor_position > char_count {
780 self.cursor_position = char_count;
781 }
782
783 let mut chars: Vec<char> = self.current_input.chars().collect();
784 chars.insert(self.cursor_position, c);
785 self.current_input = chars.into_iter().collect();
786 self.cursor_position += 1;
787 }
788
789 pub fn info(&mut self, message: &str) {
810 let _ = self.print_log_entry(&get_info!(message, "Stream"));
811 }
812
813 pub fn debug(&mut self, message: &str) {
827 let _ = self.print_log_entry(&get_debug!(message, "Stream"));
828 }
829
830 pub fn warn(&mut self, message: &str) {
844 let _ = self.print_log_entry(&get_warn!(message, "Stream"));
845 }
846
847 pub fn error(&mut self, message: &str) {
861 let _ = self.print_log_entry(&get_error!(message, "Stream"));
862 }
863
864 pub fn critical(&mut self, message: &str) {
878 let _ = self.print_log_entry(&get_critical!(message, "Stream"));
879 }
880
881 async fn handle_command_result(
883 &mut self,
884 result: CommandResult,
885 ) -> Result<(), Box<dyn std::error::Error>> {
886 if !result.output.is_empty() {
887 for line in result.output.lines() {
888 self.print_log_entry(line.trim_start());
889 }
890 }
891 Ok(())
892 }
893
894 async fn check_running_commands(&mut self) -> Result<(), Box<dyn std::error::Error>> {
896 let mut completed_indices = Vec::new();
897
898 for (i, cmd) in self.running_commands.iter().enumerate() {
899 if cmd.handle.is_finished() {
900 let _ = &cmd.command;
902 completed_indices.push(i);
903 }
904 }
905
906 for &i in completed_indices.iter().rev() {
908 self.running_commands.remove(i);
909 }
910
911 Ok(())
912 }
913
914 async fn spawn_async_command(
916 &mut self,
917 command: String,
918 mut handler: Box<dyn AsyncCommandHandler>,
919 ) -> Result<(), Box<dyn std::error::Error>> {
920 let parts: Vec<&str> = command.split_whitespace().collect();
921 let args = if parts.len() > 1 { &parts[1..] } else { &[] };
922 let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();
923 let tx = self.command_result_tx.as_ref().unwrap().clone();
924 let cmd_copy = command.clone();
925 let action_sender = self.action_sender.clone();
927
928 let handle = tokio::spawn(async move {
929 let mut temp_app = TerminalApp::new();
931 if let Some(sender) = action_sender {
933 temp_app.set_action_sender(sender);
934 }
935 let args_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
936 let result = handler.execute_async(&mut temp_app, &args_refs).await;
937
938 let _ = tx.send(CommandResult {
939 command: cmd_copy,
940 output: result.clone(),
941 });
942
943 result
944 });
945
946 self.running_commands
947 .push(RunningCommand { command, handle });
948
949 Ok(())
950 }
951}