mirror of
https://github.com/alexpasmantier/television.git
synced 2025-07-28 22:01:39 +00:00
feat(config): add support for custom input prompt in user config
- Allow users to specify a custom input prompt string via the `input_prompt` setting in the configuration file. - Document the new `input_prompt` option in `.config/config.toml` with a commented example. - Add a test to ensure that the input prompt is correctly loaded from the user configuration file.
This commit is contained in:
parent
28f58e0641
commit
c5f4e65942
@ -56,6 +56,8 @@ ui_scale = 100
|
||||
|
||||
# Where to place the input bar in the UI (top or bottom)
|
||||
input_bar_position = "top"
|
||||
# The input prompt string (defaults to ">" if not specified)
|
||||
# input_prompt = ">"
|
||||
# What orientation should tv be (landscape or portrait)
|
||||
orientation = "landscape"
|
||||
# The theme to use for the UI
|
||||
|
@ -379,6 +379,8 @@ pub struct UiSpec {
|
||||
pub input_bar_position: Option<InputPosition>,
|
||||
#[serde(default)]
|
||||
pub input_header: Option<Template>,
|
||||
#[serde(default)]
|
||||
pub input_prompt: Option<String>,
|
||||
// Feature-specific configurations
|
||||
#[serde(default)]
|
||||
pub preview_panel: Option<ui::PreviewPanelConfig>,
|
||||
@ -400,6 +402,7 @@ impl From<&crate::config::UiConfig> for UiSpec {
|
||||
orientation: Some(config.orientation),
|
||||
input_bar_position: Some(config.input_bar_position),
|
||||
input_header: config.input_header.clone(),
|
||||
input_prompt: config.input_prompt.clone(),
|
||||
preview_panel: Some(config.preview_panel.clone()),
|
||||
status_bar: Some(config.status_bar.clone()),
|
||||
help_panel: Some(config.help_panel.clone()),
|
||||
|
@ -141,6 +141,16 @@ pub struct Cli {
|
||||
#[arg(long = "input-header", value_name = "STRING", verbatim_doc_comment)]
|
||||
pub input_header: Option<String>,
|
||||
|
||||
/// Input prompt string
|
||||
///
|
||||
/// When a channel is specified: This overrides the prompt defined in the channel prototype.
|
||||
/// When no channel is specified: Sets the input prompt for the ad-hoc channel.
|
||||
///
|
||||
/// The given value is used as the prompt string shown before the input field.
|
||||
/// Defaults to "> " when omitted.
|
||||
#[arg(long = "input-prompt", value_name = "STRING", verbatim_doc_comment)]
|
||||
pub input_prompt: Option<String>,
|
||||
|
||||
/// Preview header template
|
||||
///
|
||||
/// When a channel is specified: This overrides the header defined in the channel prototype.
|
||||
|
@ -80,6 +80,7 @@ pub struct PostProcessedCli {
|
||||
// Input configuration
|
||||
pub input: Option<String>,
|
||||
pub input_header: Option<String>,
|
||||
pub input_prompt: Option<String>,
|
||||
|
||||
// UI and layout configuration
|
||||
pub layout: Option<Orientation>,
|
||||
@ -150,6 +151,7 @@ impl Default for PostProcessedCli {
|
||||
// Input configuration
|
||||
input: None,
|
||||
input_header: None,
|
||||
input_prompt: None,
|
||||
|
||||
// UI and layout configuration
|
||||
layout: None,
|
||||
@ -340,6 +342,7 @@ pub fn post_process(cli: Cli, readable_stdin: bool) -> PostProcessedCli {
|
||||
// Input configuration
|
||||
input: cli.input,
|
||||
input_header: cli.input_header,
|
||||
input_prompt: cli.input_prompt,
|
||||
|
||||
// UI and layout configuration
|
||||
layout,
|
||||
|
@ -266,6 +266,11 @@ impl Config {
|
||||
self.ui.input_header = Some(value.clone());
|
||||
}
|
||||
|
||||
// Apply input_prompt
|
||||
if let Some(value) = &ui_spec.input_prompt {
|
||||
self.ui.input_prompt = Some(value.clone());
|
||||
}
|
||||
|
||||
// Handle preview_panel with field merging
|
||||
if let Some(preview_panel) = &ui_spec.preview_panel {
|
||||
self.ui.preview_panel.size = preview_panel.size;
|
||||
@ -481,6 +486,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
const USER_CONFIG_INPUT_PROMPT: &str = r#"
|
||||
[ui]
|
||||
input_prompt = "❯ "
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn test_config_input_prompt_from_user_cfg() {
|
||||
// write user config to a file
|
||||
let dir = tempdir().unwrap();
|
||||
let config_dir = dir.path();
|
||||
let config_file = config_dir.join(CONFIG_FILE_NAME);
|
||||
let mut file = File::create(&config_file).unwrap();
|
||||
file.write_all(USER_CONFIG_INPUT_PROMPT.as_bytes()).unwrap();
|
||||
|
||||
let config_env = ConfigEnv {
|
||||
_data_dir: get_data_dir(),
|
||||
config_dir: config_dir.to_path_buf(),
|
||||
};
|
||||
let config = Config::new(&config_env, None).unwrap();
|
||||
|
||||
// Verify that input_prompt was loaded from user config
|
||||
assert_eq!(config.ui.input_prompt, Some("❯ ".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setting_user_shell_integration_triggers_overrides_default() {
|
||||
let user_config = r#"
|
||||
|
@ -108,6 +108,7 @@ pub struct UiConfig {
|
||||
pub orientation: Orientation,
|
||||
pub theme: String,
|
||||
pub input_header: Option<Template>,
|
||||
pub input_prompt: Option<String>,
|
||||
pub features: Features,
|
||||
|
||||
// Feature-specific configurations
|
||||
@ -130,6 +131,7 @@ impl Default for UiConfig {
|
||||
orientation: Orientation::Landscape,
|
||||
theme: String::from(DEFAULT_THEME),
|
||||
input_header: None,
|
||||
input_prompt: None,
|
||||
features: Features::default(),
|
||||
status_bar: StatusBarConfig::default(),
|
||||
preview_panel: PreviewPanelConfig::default(),
|
||||
|
@ -197,6 +197,11 @@ pub fn draw(ctx: Box<Ctx>, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
||||
)?;
|
||||
|
||||
// input box
|
||||
let input_prompt = ctx.config.ui.input_prompt
|
||||
.as_ref()
|
||||
.map(|p| format!("{} ", p))
|
||||
.unwrap_or_else(|| "> ".to_string());
|
||||
|
||||
draw_input_box(
|
||||
f,
|
||||
layout.input,
|
||||
@ -209,6 +214,7 @@ pub fn draw(ctx: Box<Ctx>, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
||||
&ctx.tv_state.spinner,
|
||||
&ctx.colorscheme,
|
||||
&ctx.config.ui.input_header,
|
||||
&input_prompt,
|
||||
&ctx.config.ui.input_bar_position,
|
||||
)?;
|
||||
|
||||
|
@ -202,6 +202,9 @@ fn apply_cli_overrides(args: &PostProcessedCli, config: &mut Config) {
|
||||
config.ui.input_header = Some(t);
|
||||
}
|
||||
}
|
||||
if let Some(input_prompt) = &args.input_prompt {
|
||||
config.ui.input_prompt = Some(input_prompt.clone());
|
||||
}
|
||||
if let Some(preview_header) = &args.preview_header {
|
||||
if let Ok(t) = Template::parse(preview_header) {
|
||||
config.ui.preview_panel.header = Some(t);
|
||||
@ -372,6 +375,7 @@ fn apply_ui_overrides(
|
||||
orientation: None,
|
||||
input_bar_position: None,
|
||||
input_header: None,
|
||||
input_prompt: None,
|
||||
preview_panel: None,
|
||||
status_bar: None,
|
||||
help_panel: None,
|
||||
@ -386,6 +390,12 @@ fn apply_ui_overrides(
|
||||
}
|
||||
}
|
||||
|
||||
// Apply input prompt override
|
||||
if let Some(input_prompt_str) = &args.input_prompt {
|
||||
ui_spec.input_prompt = Some(input_prompt_str.clone());
|
||||
ui_changes_needed = true;
|
||||
}
|
||||
|
||||
// Apply layout/orientation override
|
||||
if let Some(layout) = args.layout {
|
||||
ui_spec.orientation = Some(layout);
|
||||
@ -737,6 +747,7 @@ mod tests {
|
||||
orientation: Some(Orientation::Portrait),
|
||||
input_bar_position: None,
|
||||
input_header: Some(Template::parse("Original Header").unwrap()),
|
||||
input_prompt: None,
|
||||
preview_panel: Some(television::config::ui::PreviewPanelConfig {
|
||||
size: 50,
|
||||
header: Some(
|
||||
|
@ -29,6 +29,7 @@ pub fn draw_input_box(
|
||||
spinner: &Spinner,
|
||||
colorscheme: &Colorscheme,
|
||||
input_header: &Option<Template>,
|
||||
input_prompt: &str,
|
||||
input_bar_position: &InputPosition,
|
||||
) -> Result<()> {
|
||||
let header = input_header
|
||||
@ -65,7 +66,7 @@ pub fn draw_input_box(
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
// prompt symbol
|
||||
Constraint::Length(2),
|
||||
Constraint::Length(u16::try_from(input_prompt.len()).unwrap_or(2)),
|
||||
// input field
|
||||
Constraint::Fill(1),
|
||||
// result count
|
||||
@ -81,7 +82,7 @@ pub fn draw_input_box(
|
||||
|
||||
let arrow_block = Block::default();
|
||||
let arrow = Paragraph::new(Span::styled(
|
||||
"> ",
|
||||
input_prompt,
|
||||
Style::default().fg(colorscheme.input.input_fg).bold(),
|
||||
))
|
||||
.block(arrow_block);
|
||||
|
@ -89,6 +89,63 @@ fn test_input_header_in_adhoc_mode() {
|
||||
PtyTester::assert_exit_ok(&mut child, DEFAULT_DELAY);
|
||||
}
|
||||
|
||||
/// Tests that --input-prompt customizes the prompt symbol in Channel Mode.
|
||||
#[test]
|
||||
fn test_input_prompt_in_channel_mode() {
|
||||
let mut tester = PtyTester::new();
|
||||
|
||||
// This overrides the channel's default input prompt with custom symbol
|
||||
let mut cmd = tv_local_config_and_cable_with_args(&["files"]);
|
||||
cmd.args(["--input-prompt", "❯ "]);
|
||||
let mut child = tester.spawn_command_tui(cmd);
|
||||
|
||||
// Verify the custom input prompt is displayed
|
||||
tester.assert_tui_frame_contains("❯ ");
|
||||
tester.assert_tui_frame_contains("CHANNEL files");
|
||||
|
||||
// Send Ctrl+C to exit
|
||||
tester.send(&ctrl('c'));
|
||||
PtyTester::assert_exit_ok(&mut child, DEFAULT_DELAY);
|
||||
}
|
||||
|
||||
/// Tests that --input-prompt works in Ad-hoc Mode.
|
||||
#[test]
|
||||
fn test_input_prompt_in_adhoc_mode() {
|
||||
let mut tester = PtyTester::new();
|
||||
|
||||
// This provides a custom input prompt for an ad-hoc channel
|
||||
let mut cmd =
|
||||
tv_local_config_and_cable_with_args(&["--source-command", "ls"]);
|
||||
cmd.args(["--input-prompt", "→ "]);
|
||||
let mut child = tester.spawn_command_tui(cmd);
|
||||
|
||||
// Verify the custom input prompt is displayed
|
||||
tester.assert_tui_frame_contains("→ ");
|
||||
tester.assert_tui_frame_contains("CHANNEL custom");
|
||||
|
||||
// Send Ctrl+C to exit
|
||||
tester.send(&ctrl('c'));
|
||||
PtyTester::assert_exit_ok(&mut child, DEFAULT_DELAY);
|
||||
}
|
||||
|
||||
/// Tests that the default input prompt "> " is used when no custom prompt is specified.
|
||||
#[test]
|
||||
fn test_default_input_prompt() {
|
||||
let mut tester = PtyTester::new();
|
||||
|
||||
// This uses the default input prompt
|
||||
let cmd = tv_local_config_and_cable_with_args(&["files"]);
|
||||
let mut child = tester.spawn_command_tui(cmd);
|
||||
|
||||
// Verify the default input prompt is displayed
|
||||
tester.assert_tui_frame_contains("> ");
|
||||
tester.assert_tui_frame_contains("CHANNEL files");
|
||||
|
||||
// Send Ctrl+C to exit
|
||||
tester.send(&ctrl('c'));
|
||||
PtyTester::assert_exit_ok(&mut child, DEFAULT_DELAY);
|
||||
}
|
||||
|
||||
/// Tests that --ui-scale adjusts the overall interface size.
|
||||
#[test]
|
||||
fn test_ui_scale() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user