diff --git a/crates/television/action.rs b/crates/television/action.rs index 29312d5..d6c85da 100644 --- a/crates/television/action.rs +++ b/crates/television/action.rs @@ -1,48 +1,71 @@ use serde::{Deserialize, Serialize}; use strum::Display; +/// The different actions that can be performed by the application. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display)] pub enum Action { // input actions + /// Add a character to the input buffer. AddInputChar(char), + /// Delete the character before the cursor from the input buffer. DeletePrevChar, + /// Delete the character after the cursor from the input buffer. DeleteNextChar, + /// Move the cursor to the character before the current cursor position. GoToPrevChar, + /// Move the cursor to the character after the current cursor position. GoToNextChar, + /// Move the cursor to the start of the input buffer. GoToInputStart, + /// Move the cursor to the end of the input buffer. GoToInputEnd, // rendering actions + /// Render the terminal user interface screen. Render, + /// Resize the terminal user interface screen to the given dimensions. Resize(u16, u16), + /// Clear the terminal user interface screen. ClearScreen, // results actions + /// Select the entry currently under the cursor. SelectEntry, + /// Select the entry currently under the cursor and exit the application. SelectAndExit, + /// Select the next entry in the currently focused list. SelectNextEntry, + /// Select the previous entry in the currently focused list. SelectPrevEntry, + /// Copy the currently selected entry to the clipboard. CopyEntryToClipboard, - // navigation actions - GoToPaneUp, - GoToPaneDown, - GoToPaneLeft, - GoToPaneRight, - GoToNextPane, - GoToPrevPane, // preview actions + /// Scroll the preview up by one line. ScrollPreviewUp, + /// Scroll the preview down by one line. ScrollPreviewDown, + /// Scroll the preview up by half a page. ScrollPreviewHalfPageUp, + /// Scroll the preview down by half a page. ScrollPreviewHalfPageDown, + /// Open the currently selected entry in the default application. OpenEntry, // application actions + /// Tick the application state. Tick, + /// Suspend the application. Suspend, + /// Resume the application. Resume, + /// Quit the application. Quit, + /// Toggle the help screen. Help, + /// Signal an error with the given message. Error(String), + /// No operation. NoOp, // channel actions + /// Toggle the remote control channel. ToggleRemoteControl, + /// Toggle the remote control in `send to channel` mode. ToggleSendToChannel, } diff --git a/crates/television/app.rs b/crates/television/app.rs index 537d89d..5054aac 100644 --- a/crates/television/app.rs +++ b/crates/television/app.rs @@ -14,19 +14,29 @@ use crate::{ render::{render, RenderingTask}, }; +/// The main application struct that holds the state of the application. pub struct App { + /// The configuration of the application. config: Config, // maybe move these two into config instead of passing them // via the cli? tick_rate: f64, frame_rate: f64, + /// The television instance that handles channels and entries. television: Arc>, + /// A flag that indicates whether the application should quit during the next frame. should_quit: bool, + /// A flag that indicates whether the application should suspend during the next frame. should_suspend: bool, + /// A sender channel for actions. action_tx: mpsc::UnboundedSender, + /// The receiver channel for actions. action_rx: mpsc::UnboundedReceiver, + /// The receiver channel for events. event_rx: mpsc::UnboundedReceiver>, + /// A sender channel to abort the event loop. event_abort_tx: mpsc::UnboundedSender<()>, + /// A sender channel for rendering tasks. render_tx: mpsc::UnboundedSender, } @@ -57,6 +67,20 @@ impl App { }) } + /// Run the application main loop. + /// + /// This function will start the event loop and the rendering loop and handle + /// all actions that are sent to the application. + /// The function will return the selected entry if the application is exited. + /// + /// # Arguments + /// * `is_output_tty` - A flag that indicates whether the output is a tty. + /// + /// # Returns + /// The selected entry (if any) if the application is exited. + /// + /// # Errors + /// If an error occurs during the execution of the application. pub async fn run(&mut self, is_output_tty: bool) -> Result> { info!("Starting backend event loop"); let event_loop = EventLoop::new(self.tick_rate, true); @@ -107,6 +131,16 @@ impl App { } } + /// Convert an event to an action. + /// + /// This function will convert an event to an action based on the current + /// mode the television is in. + /// + /// # Arguments + /// * `event` - The event to convert to an action. + /// + /// # Returns + /// The action that corresponds to the given event. async fn convert_event_to_action(&self, event: Event) -> Action { match event { Event::Input(keycode) => { @@ -144,6 +178,16 @@ impl App { } } + /// Handle actions. + /// + /// This function will handle all actions that are sent to the application. + /// The function will return the selected entry if the application is exited. + /// + /// # Returns + /// The selected entry (if any) if the application is exited. + /// + /// # Errors + /// If an error occurs during the execution of the application. async fn handle_actions(&mut self) -> Result> { while let Ok(action) = self.action_rx.try_recv() { if action != Action::Tick && action != Action::Render { diff --git a/crates/television/channels.rs b/crates/television/channels.rs index c57f6a1..3c99169 100644 --- a/crates/television/channels.rs +++ b/crates/television/channels.rs @@ -12,10 +12,11 @@ mod text; /// The interface that all television channels must implement. /// -/// # Important -/// The `OnAir` requires the `Send` trait to be implemented as -/// well. This is necessary to allow the channels to be used in a -/// multithreaded environment. +/// # Note +/// The `OnAir` trait requires the `Send` trait to be implemented as well. +/// This is necessary to allow the channels to be used with the tokio +/// runtime, which requires `Send` in order to be able to send tasks between +/// worker threads safely. /// /// # Methods /// - `find`: Find entries that match the given pattern. This method does not @@ -139,6 +140,25 @@ pub enum TelevisionChannel { RemoteControl(remote_control::RemoteControl), } +/// NOTE: this could/should be generated by a macro +impl TryFrom<&Entry> for TelevisionChannel { + type Error = String; + + fn try_from(entry: &Entry) -> Result { + match entry.name.to_ascii_lowercase().as_ref() { + "env" => Ok(TelevisionChannel::Env(env::Channel::default())), + "files" => Ok(TelevisionChannel::Files(files::Channel::default())), + "gitrepos" => { + Ok(TelevisionChannel::GitRepos(git_repos::Channel::default())) + } + "text" => Ok(TelevisionChannel::Text(text::Channel::default())), + "stdin" => Ok(TelevisionChannel::Stdin(stdin::Channel::default())), + "alias" => Ok(TelevisionChannel::Alias(alias::Channel::default())), + _ => Err(format!("Unknown channel: {}", entry.name)), + } + } +} + macro_rules! variant_to_module { (Files) => { files::Channel @@ -247,27 +267,13 @@ macro_rules! define_transitions { } } +// Define the transitions between the different channels. +// +// This is where the transitions between the different channels are defined. +// The transitions are defined as a list of tuples where the first element +// is the source channel and the second element is a list of potential target channels. define_transitions! { Text => [Files, Text], Files => [Files, Text], GitRepos => [Files, Text], } - -/// NOTE: this could/should be generated by a macro -impl TryFrom<&Entry> for TelevisionChannel { - type Error = String; - - fn try_from(entry: &Entry) -> Result { - match entry.name.to_ascii_lowercase().as_ref() { - "env" => Ok(TelevisionChannel::Env(env::Channel::default())), - "files" => Ok(TelevisionChannel::Files(files::Channel::default())), - "gitrepos" => { - Ok(TelevisionChannel::GitRepos(git_repos::Channel::default())) - } - "text" => Ok(TelevisionChannel::Text(text::Channel::default())), - "stdin" => Ok(TelevisionChannel::Stdin(stdin::Channel::default())), - "alias" => Ok(TelevisionChannel::Alias(alias::Channel::default())), - _ => Err(format!("Unknown channel: {}", entry.name)), - } - } -} diff --git a/crates/television/entry.rs b/crates/television/entry.rs index 771ec9d..4c40e87 100644 --- a/crates/television/entry.rs +++ b/crates/television/entry.rs @@ -2,25 +2,56 @@ use devicons::FileIcon; use crate::previewers::PreviewType; -/// NOTE: having an enum for entry types would be nice since it would allow -/// having a nicer implementation for transitions between channels. This would -/// permit implementing `From` for channels which would make the -/// channel convertible from any other that yields `EntryType`. -/// This needs pondering since it does bring another level of abstraction and -/// adds a layer of complexity. +// NOTE: having an enum for entry types would be nice since it would allow +// having a nicer implementation for transitions between channels. This would +// permit implementing `From` for channels which would make the +// channel convertible from any other that yields `EntryType`. +// This needs pondering since it does bring another level of abstraction and +// adds a layer of complexity. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct Entry { + /// The name of the entry. pub name: String, + /// The display name of the entry. display_name: Option, + /// An optional value associated with the entry. pub value: Option, + /// The optional ranges for matching characters in the name. pub name_match_ranges: Option>, + /// The optional ranges for matching characters in the value. pub value_match_ranges: Option>, + /// The optional icon associated with the entry. pub icon: Option, + /// The optional line number associated with the entry. pub line_number: Option, + /// The type of preview associated with the entry. pub preview_type: PreviewType, } impl Entry { + /// Create a new entry with the given name and preview type. + /// + /// Additional fields can be set using the builder pattern. + /// ``` + /// use television::entry::{Entry, PreviewType}; + /// use television::devicons::FileIcon; + /// + /// let entry = Entry::new("name".to_string(), PreviewType::EnvVar) + /// .with_display_name("display_name".to_string()) + /// .with_value("value".to_string()) + /// .with_name_match_ranges(vec![(0, 1)]) + /// .with_value_match_ranges(vec![(0, 1)]) + /// .with_icon(FileIcon::default()) + /// .with_line_number(0); + /// ``` + /// + /// # Arguments + /// * `name` - The name of the entry. + /// * `preview_type` - The type of preview associated with the entry. + /// + /// # Returns + /// A new entry with the given name and preview type. + /// The other fields are set to `None` by default. pub fn new(name: String, preview_type: PreviewType) -> Self { Self { name,