daemon_console/
lib.rs

1//! # daemon_console
2//!
3//! A flexible console for daemon applications providing a terminal interface
4//! with command registration, history navigation, and colored logging.
5//!
6//! # Examples
7//!
8//! A simple way to create a `TerminalApp` instance.
9//!
10//! ```rust
11//! use daemon_console::TerminalApp;
12//! use std::io::stdout;
13//!
14//! #[tokio::main]
15//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
16//!     let mut app = TerminalApp::new();
17//!     Ok(())
18//! }
19//! ```
20//!
21//! See more details in `src/main.rs` in source code.
22
23#![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
45/// Actions that can be sent from async commands to the main application
46pub enum AppAction {
47    /// Register a new command: (command_name, handler)
48    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
63/// Type alias for custom unknown command handlers.
64type UnknownCommandHandler = Box<dyn Fn(&str) -> String + Send + Sync + 'static>;
65
66/// Type alias for async unknown command handlers.
67type AsyncUnknownCommandHandler =
68    Box<dyn Fn(&str) -> BoxFuture<'static, String> + Send + Sync + 'static>;
69
70/// Result from command execution
71#[derive(Debug)]
72pub struct CommandResult {
73    pub command: String,
74    pub output: String,
75}
76
77/// Status of running commands
78#[derive(Debug)]
79struct RunningCommand {
80    pub command: String,
81    pub handle: JoinHandle<String>,
82}
83
84/// Trait for synchronous command handlers that can be registered with the terminal application.
85///
86/// All synchronous commands must implement this trait to be executable within the terminal app.
87/// Handlers receive mutable access to the application state and command arguments.
88pub trait CommandHandler: Send + Sync + 'static {
89    /// Executes the command with the given application state and arguments.
90    ///
91    /// # Arguments
92    ///
93    /// * `app` - Mutable reference to the terminal application
94    /// * `args` - Slice of command arguments
95    ///
96    /// # Returns
97    ///
98    /// String output to be displayed to the user
99    fn execute(&mut self, app: &mut TerminalApp, args: &[&str]) -> String;
100}
101
102/// Trait for asynchronous command handlers that can be registered with the terminal application.
103///
104/// All asynchronous commands must implement this trait to be executable within the terminal app.
105/// Handlers receive mutable access to the application state and command arguments.
106#[async_trait]
107pub trait AsyncCommandHandler: Send + Sync + 'static {
108    /// Executes the command asynchronously with the given application state and arguments.
109    ///
110    /// # Arguments
111    ///
112    /// * `app` - Mutable reference to the terminal application
113    /// * `args` - Slice of command arguments
114    ///
115    /// # Returns
116    ///
117    /// String output to be displayed to the user
118    async fn execute_async(&mut self, app: &mut TerminalApp, args: &[&str]) -> String;
119
120    /// Creates a boxed clone of this handler for reuse
121    fn box_clone(&self) -> Box<dyn AsyncCommandHandler>;
122}
123
124/// Internal enum to hold either sync or async command handlers.
125enum CommandHandlerType {
126    Sync(Box<dyn CommandHandler>),
127    Async(Box<dyn AsyncCommandHandler>),
128}
129
130/// Blanket implementation of `CommandHandler` for closures.
131///
132/// This allows simple closures to be used as command handlers without
133/// explicitly implementing the trait.
134impl<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
143/// Main terminal application structure managing state and command execution.
144///
145/// `TerminalApp` provides a complete terminal interface with:
146/// - Command history navigation
147/// - Cursor management
148/// - Custom command registration (sync and async)
149/// - Configurable unknown command handling
150/// - Non-blocking async command execution
151pub 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    /// Removes a character at a specific index in a string.
177    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    /// Creates a new terminal application instance with default settings.
186    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    /// Registers a synchronous command handler with the application.
208    ///
209    /// # Arguments
210    ///
211    /// * `name` - Command name that users will type
212    /// * `handler` - Boxed command handler implementing `CommandHandler`
213    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    /// Registers an asynchronous command handler with the application.
219    ///
220    /// # Arguments
221    ///
222    /// * `name` - Command name that users will type
223    /// * `handler` - Boxed async command handler implementing `AsyncCommandHandler`
224    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    /// Sets the action sender for communication with async commands
234    ///
235    /// # Arguments
236    ///
237    /// * `sender` - The sender to use for sending actions from async commands
238    pub(crate) fn set_action_sender(&mut self, sender: mpsc::UnboundedSender<AppAction>) {
239        self.action_sender = Some(sender);
240    }
241
242    /// Sets a custom handler for unknown commands (synchronous).
243    ///
244    /// # Arguments
245    ///
246    /// * `handler` - Closure that takes the full command string and returns a response
247    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    /// Sets a custom handler for unknown commands (asynchronous).
255    ///
256    /// # Arguments
257    ///
258    /// * `handler` - Closure that takes the full command string and returns a future
259    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    /// Removes the custom unknown command handler.
267    pub fn clear_unknown_command_handler(&mut self) {
268        self.unknown_command_handler = None;
269        self.async_unknown_command_handler = None;
270    }
271
272    /// Initializes the terminal with raw mode and displays startup messages.
273    ///
274    /// # Arguments
275    ///
276    /// * `startup_message` - Message to display on startup
277    ///
278    /// # Errors
279    ///
280    /// Returns an error if terminal initialization fails.
281    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    /// 设置终端模式和鼠标捕获
295    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    /// Processes a single terminal event and returns whether the app should quit.
312    ///
313    /// # Arguments
314    ///
315    /// * `event` - Terminal event to process
316    /// * `stdout` - Mutable reference to standard output
317    ///
318    /// # Returns
319    ///
320    /// `Ok(true)` if the application should exit, `Ok(false)` otherwise
321    ///
322    /// # Errors
323    ///
324    /// Returns an error if event processing fails.
325    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            // Debugging codes, so fuck you Windows
333            // fyi: https://github.com/crossterm-rs/crossterm/pull/745
334            //
335            // if key_event.kind == KeyEventKind::Press {
336            //     println!("[raw_debug]Key press: {}", key_event.code)
337            // }
338            // if key_event.kind == KeyEventKind::Release {
339            //     println!("[raw_debug]Key release: {}", key_event.code);
340            // }
341            //
342
343            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    /// Shuts down the terminal and displays exit messages.
432    ///
433    /// # Arguments
434    ///
435    /// * `stdout` - Mutable reference to standard output
436    /// * `exit_message` - Message to display on exit
437    ///
438    /// # Errors
439    ///
440    /// Returns an error if terminal shutdown fails.
441    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    /// Main application loop that handles terminal input and command execution.
452    ///
453    /// Initializes terminal event handling, processes keyboard input, and manages
454    /// command execution until exit is requested. Handles special key combinations
455    /// like Ctrl+C for graceful shutdown.
456    ///
457    /// # Arguments
458    ///
459    /// * `startup_message` - Optional message to display on startup
460    /// * `exit_message` - Optional message to display on exit
461    ///
462    /// # Errors
463    ///
464    /// Returns an error if terminal initialization or event handling fails.
465    pub async fn run(
466        &mut self,
467        startup_message: &str,
468        exit_message: &str,
469    ) -> Result<(), Box<dyn std::error::Error>> {
470        // Create channel for AppAction messages
471        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            // Handle AppAction messages
483            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            // Check for completed async commands
492            self.check_running_commands().await?;
493
494            // Process command results
495            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            // Handle terminal events (non-blocking)
502            tokio::select! {
503                _ = tokio::time::sleep(tokio::time::Duration::from_millis(50)) => {
504                    // Check if events are available without blocking
505                    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    /// Clear the current input line and re-renders it.
531    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    /// Prints a log entry while preserving the input line.
540    ///
541    /// Clears the current line, prints the log message, and re-renders the input line.
542    ///
543    /// # Arguments
544    ///
545    /// * `stdout` - Mutable reference to standard output
546    /// * `log_line` - Log message to display
547    ///
548    /// # Errors
549    ///
550    /// Returns an error if writing to stdout fails.
551    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    /// Renders the input line with prompt and cursor positioning.
559    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    /// Handles Ctrl+D key press, signaling application exit.
589    ///
590    /// # Returns
591    ///
592    /// `Ok(true)` to signal the application should quit.
593    pub async fn handle_ctrl_d(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
594        Ok(true)
595    }
596
597    /// Handles Ctrl+C key press with double-press confirmation.
598    ///
599    /// The first press clears input, the second press within 5 seconds exits.
600    ///
601    /// # Returns
602    ///
603    /// Tuple of (should_quit, message_to_display)
604    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    /// Handles up the arrow key press for command history navigation.
633    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    /// Handles down the arrow key press for command history navigation.
648    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    /// Handles Enter key press to execute the current command.
665    ///
666    /// # Arguments
667    ///
668    /// * `stdout` - Mutable reference to standard output
669    /// * `input_prefix` - Prefix to display before echoing the command
670    ///
671    /// # Errors
672    ///
673    /// Returns an error if writing to stdout fails.
674    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    /// Executes a command by looking it up in the registered commands.
704    ///
705    /// For sync commands, executes immediately and returns the result.
706    /// For async commands, spawns them in the background and returns immediately.
707    ///
708    /// # Arguments
709    ///
710    /// * `command` - Full command string including arguments
711    ///
712    /// # Returns
713    ///
714    /// String output from the command execution (empty for async commands)
715    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                    // Remove, execute, and put back sync handler
728                    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                    // Clone the async handler for execution
741                    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    /// Handles character input by inserting at the cursor position.
776    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    /// Log info-level messages.
790    ///
791    /// This method ensures proper terminal line management by clearing the current
792    /// input line, printing the log message, and then re-rendering the input line.
793    ///
794    /// # Arguments
795    ///
796    /// * `message` - The message content to be logged.
797    ///
798    /// # Examples
799    ///
800    /// ```rust
801    /// use daemon_console::TerminalApp;
802    ///
803    /// fn main() {
804    ///     let mut app = TerminalApp::new();
805    ///     app.info("Application started successfully!");
806    ///     app.info("Running tasks...");
807    /// }
808    /// ```
809    pub fn info(&mut self, message: &str) {
810        let _ = self.print_log_entry(&get_info!(message, "Stream"));
811    }
812
813    /// Log debug-level messages.
814    ///
815    /// # Examples
816    ///
817    /// ```
818    /// use daemon_console::TerminalApp;
819    ///
820    /// fn main() {
821    ///     let mut app = TerminalApp::new();
822    ///     app.debug("Debugging information...");
823    ///     app.debug("Debugging more...");
824    /// }
825    /// ```
826    pub fn debug(&mut self, message: &str) {
827        let _ = self.print_log_entry(&get_debug!(message, "Stream"));
828    }
829
830    /// Log warn-level messages.
831    ///
832    /// # Examples
833    ///
834    /// ```
835    /// use daemon_console::TerminalApp;
836    ///
837    /// fn main() {
838    ///     let mut app = TerminalApp::new();
839    ///     app.warn("You get a warning!");
840    ///     app.warn("Continue running...");
841    /// }
842    /// ```
843    pub fn warn(&mut self, message: &str) {
844        let _ = self.print_log_entry(&get_warn!(message, "Stream"));
845    }
846
847    /// Log error-level messages.
848    ///
849    /// # Examples
850    ///
851    /// ```
852    /// use daemon_console::TerminalApp;
853    ///
854    /// fn main() {
855    ///     let mut app = TerminalApp::new();
856    ///     app.error("An error occurred!");
857    ///     app.error("Failed to run tasks.");
858    /// }
859    /// ```
860    pub fn error(&mut self, message: &str) {
861        let _ = self.print_log_entry(&get_error!(message, "Stream"));
862    }
863
864    /// Log critical-level messages.
865    ///
866    /// # Examples
867    ///
868    /// ```
869    /// use daemon_console::TerminalApp;
870    ///
871    /// fn main() {
872    ///     let mut app = TerminalApp::new();
873    ///     app.critical("Application crashed!");
874    ///     app.critical("Exception: unknown.");
875    /// }
876    /// ```
877    pub fn critical(&mut self, message: &str) {
878        let _ = self.print_log_entry(&get_critical!(message, "Stream"));
879    }
880
881    /// Handles completed command results from async commands
882    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    /// Checks for completed running commands and cleans up finished tasks
895    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                // Log completion (using the command field)
901                let _ = &cmd.command;
902                completed_indices.push(i);
903            }
904        }
905
906        // Remove completed commands in reverse order to maintain indices
907        for &i in completed_indices.iter().rev() {
908            self.running_commands.remove(i);
909        }
910
911        Ok(())
912    }
913
914    /// Spawns an async command in the background
915    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        // Clone action_sender to pass to the async command
926        let action_sender = self.action_sender.clone();
927
928        let handle = tokio::spawn(async move {
929            // Create a temporary app instance for the async command
930            let mut temp_app = TerminalApp::new();
931            // Set the action sender for the temporary app
932            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}