fix: minor changes

This commit is contained in:
lalvarezt 2025-07-28 15:43:50 +02:00
parent 4986dd00d3
commit 15cd53b76f
6 changed files with 80 additions and 113 deletions

View File

@ -17,3 +17,4 @@ ctrl-f12 = "actions:edit"
[actions.edit]
description = "Opens the selected entries with Neovim"
command = "nvim {}"
mode = "execute"

View File

@ -1,9 +1,8 @@
use crate::{event::Key, screen::constants::ACTION_PREFIX};
use serde::{Deserialize, Serialize};
use serde_with::{OneOrMany, serde_as};
use std::fmt::Display;
use crate::event::Key;
/// The different actions that can be performed by the application.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
@ -423,8 +422,8 @@ impl<'de> serde::Deserialize<'de> for Action {
"watch_timer" => Action::WatchTimer,
"select_prev_history" => Action::SelectPrevHistory,
"select_next_history" => Action::SelectNextHistory,
s if s.starts_with("actions:") => {
let action_name = &s[8..]; // Remove "actions:" prefix
s if s.starts_with(ACTION_PREFIX) => {
let action_name = s.trim_start_matches(ACTION_PREFIX);
Action::ExternalAction(action_name.to_string())
}
_ => {

View File

@ -686,77 +686,47 @@ impl App {
Action::ExternalAction(ref action_name) => {
debug!("External action triggered: {}", action_name);
// Handle external action execution for selected entries
let selected_entries = if !self
.television
.channel
.selected_entries()
.is_empty()
if let Some(selected_entries) =
self.television.get_selected_entries()
{
// Use multi-selected entries
self.television.channel.selected_entries().clone()
} else if let Some(current_entry) =
self.television.get_selected_entry()
{
// Use single entry under cursor
std::iter::once(current_entry).collect()
} else {
debug!("No entries available for external action");
self.action_tx.send(Action::Error(
"No entry available for external action"
.to_string(),
))?;
return Ok(ActionOutcome::None);
};
debug!(
"Selected {} entries for external action",
selected_entries.len()
);
if let Some(action_spec) = self
.television
.channel_prototype
.actions
.get(action_name)
{
debug!("Found action spec for: {}", action_name);
// Store the external action info and exit - the command will be executed after terminal cleanup
self.should_quit = true;
self.render_tx.send(RenderingTask::Quit)?;
// Concatenate entry values with space separator, quoting items with whitespace
let concatenated_entries: String =
selected_entries
.iter()
.map(|entry| {
let raw = entry.raw.clone();
if raw.chars().any(char::is_whitespace)
{
format!("'{}'", raw)
} else {
raw
}
})
.collect::<Vec<String>>()
.join(" ");
return Ok(ActionOutcome::ExternalAction(
action_spec.clone(),
concatenated_entries,
));
if let Some(action_spec) = self
.television
.channel_prototype
.actions
.get(action_name)
{
// Store the external action info and exit - the command will be executed after terminal cleanup
self.should_quit = true;
self.render_tx.send(RenderingTask::Quit)?;
// Concatenate entry values with space separator, quoting items with whitespace
let concatenated_entries: String =
selected_entries
.iter()
.map(|entry| {
let raw = entry.raw.clone();
if raw
.chars()
.any(char::is_whitespace)
{
format!("'{}'", raw)
} else {
raw
}
})
.collect::<Vec<String>>()
.join(" ");
return Ok(ActionOutcome::ExternalAction(
action_spec.clone(),
concatenated_entries,
));
}
}
error!("Unknown action: {}", action_name);
// List available actions for debugging
let available_actions: Vec<&String> = self
.television
.channel_prototype
.actions
.keys()
.collect();
debug!("Available actions: {:?}", available_actions);
self.action_tx.send(Action::Error(format!(
"Unknown action: {}",
action_name
)))?;
debug!("No entries available for external action");
self.action_tx.send(Action::Error(
"No entry available for external action"
.to_string(),
))?;
return Ok(ActionOutcome::None);
}
_ => {}
}

View File

@ -165,11 +165,11 @@ impl CommandSpec {
)]
#[serde(rename_all = "lowercase")]
pub enum ExecutionMode {
/// Fork the command as a child process (current behavior, tv stays open)
/// Fork the command as a child process (tv stays open)
#[default]
Fork,
/// Replace the current process with the command (tv exits, command takes over)
Become,
Execute,
}
/// Output handling mode for external actions
@ -178,12 +178,10 @@ pub enum ExecutionMode {
)]
#[serde(rename_all = "lowercase")]
pub enum OutputMode {
/// Inherit stdin/stdout/stderr (current behavior)
#[default]
Inherit,
/// Capture output for processing
Capture,
/// Discard output silently
#[default]
Discard,
}
@ -193,12 +191,13 @@ pub struct ActionSpec {
pub description: Option<String>,
#[serde(flatten)]
pub command: CommandSpec,
/// How to execute the command (fork vs become)
/// How to execute the command
#[serde(default)]
pub r#become: bool,
pub mode: ExecutionMode,
/// How to handle command output
#[serde(default)]
pub output_mode: OutputMode,
// TODO: add `requirements` (see `prototypes::BinaryRequirement`)
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]

View File

@ -1,5 +1,5 @@
pub const POINTER_SYMBOL: &str = "> ";
pub const SELECTED_SYMBOL: &str = "";
pub const DESELECTED_SYMBOL: &str = " ";
pub const LOGO_WIDTH: u16 = 24;
pub const ACTION_PREFIX: &str = "actions:";

View File

@ -1,11 +1,12 @@
use crate::{
channels::prototypes::{ActionSpec, ExecutionMode, OutputMode},
utils::shell::Shell,
};
use std::{collections::HashMap, process::Command};
#[cfg(not(unix))]
use tracing::warn;
use super::shell::Shell;
use crate::channels::prototypes::{ActionSpec, OutputMode};
pub fn shell_command<S>(
command: &str,
interactive: bool,
@ -52,36 +53,33 @@ pub fn execute_action(
&action_spec.command.env,
);
// Configure stdio based on output mode (future extension point)
match action_spec.output_mode {
OutputMode::Inherit => {
cmd.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
// Execute based on execution mode
match action_spec.mode {
ExecutionMode::Execute => {
// For Execute mode, let the new process inherit file descriptors naturally
// TODO: use execve to replace current process
let mut child = cmd.spawn()?;
child.wait()
}
OutputMode::Capture => {
// Future: capture output for processing
cmd.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
}
OutputMode::Discard => {
// Future: discard output silently
cmd.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
}
}
ExecutionMode::Fork => {
// For Fork mode, configure stdio based on output mode
match action_spec.output_mode {
OutputMode::Capture => {
// TODO: For now, inherit stdio (future: capture output for processing)
cmd.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
}
OutputMode::Discard => {
// Discard output silently
cmd.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
}
}
// Execute based on become flag (future extension point)
if action_spec.r#become {
// Future: use execve to replace current process
// For now, use normal execution
let mut child = cmd.spawn()?;
child.wait()
} else {
// Normal fork execution (current behavior)
let mut child = cmd.spawn()?;
child.wait()
let mut child = cmd.spawn()?;
child.wait()
}
}
}