From d495d23afeba3899605a2a8aa33df1893de2e10e Mon Sep 17 00:00:00 2001 From: SW Date: Mon, 15 Jun 2026 13:04:01 +0200 Subject: [PATCH] Add configurable shell (default from /bin/bash) for command execution --- src/app.rs | 4 +++- src/commands.rs | 43 ++++++++++++++++++++++++++----------------- src/config.rs | 48 ++++++++++++++++++++++++++++++++++++------------ src/events.rs | 4 ++-- src/main.rs | 2 +- 5 files changed, 68 insertions(+), 33 deletions(-) diff --git a/src/app.rs b/src/app.rs index 4b162cb..05b0764 100644 --- a/src/app.rs +++ b/src/app.rs @@ -13,6 +13,7 @@ pub enum Focus { pub struct App { pub data_dir: PathBuf, pub terminal: Terminal, + pub shell: String, pub groups: Vec, pub group_index: usize, pub file_index: usize, @@ -22,11 +23,12 @@ pub struct App { } impl App { - pub fn new(data_dir: PathBuf, terminal: Terminal) -> Self { + pub fn new(data_dir: PathBuf, terminal: Terminal, shell: String) -> Self { let groups = load_groups(&data_dir); App { data_dir, terminal, + shell, groups, group_index: 0, file_index: 0, diff --git a/src/commands.rs b/src/commands.rs index b367548..21ad2a1 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -9,7 +9,13 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use crate::config::Terminal; use crate::parser::{CommandFile, Section}; -pub fn run_section(section: &Section, file: &CommandFile, terminal: &Terminal, data_dir: &Path) { +pub fn run_section( + section: &Section, + file: &CommandFile, + terminal: &Terminal, + shell: &str, + data_dir: &Path, +) { let commands = if section.is_run_all() { file.sections .iter() @@ -25,12 +31,12 @@ pub fn run_section(section: &Section, file: &CommandFile, terminal: &Terminal, d } match terminal { - Terminal::Ghostty => run_in_ghostty(&commands), - _ => run_via_script(&commands, terminal, data_dir), + Terminal::Ghostty => run_in_ghostty(&commands, shell), + _ => run_via_script(&commands, terminal, shell, data_dir), } } -fn run_via_script(commands: &[String], terminal: &Terminal, data_dir: &Path) { +fn run_via_script(commands: &[String], terminal: &Terminal, shell: &str, data_dir: &Path) { let script = commands.join("\n"); let run_dir = data_dir.join(".run"); if fs::create_dir_all(&run_dir).is_err() { @@ -57,35 +63,36 @@ fn run_via_script(commands: &[String], terminal: &Terminal, data_dir: &Path) { let _ = fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)); match terminal { - Terminal::Iterm => run_in_iterm(&script_path), - Terminal::Builtin => run_in_terminal_app(&script_path), + Terminal::Iterm => run_in_iterm(&script_path, shell), + Terminal::Builtin => run_in_terminal_app(&script_path, shell), Terminal::Ghostty => unreachable!(), } } -fn run_in_ghostty(commands: &[String]) { +fn run_in_ghostty(commands: &[String], shell: &str) { let script = commands.join("\n"); let encoded = STANDARD.encode(script); - let shell_command = format!("echo {} | base64 -D | bash", encoded); + let shell_command = format!("echo {} | base64 -D | {}", encoded, shell); let _ = Command::new("open") .arg("-na") .arg("Ghostty") .arg("--args") .arg("-e") - .arg("bash") + .arg(shell) .arg("-c") .arg(shell_command) .spawn(); } -fn run_in_terminal_app(script_path: &Path) { +fn run_in_terminal_app(script_path: &Path, shell: &str) { let path_str = script_path.to_string_lossy(); let applescript = format!( r#"tell application "Terminal" - do script "clear; bash {}" + do script "clear; {} {}" activate end tell"#, + shell, escape_applescript(&path_str) ); @@ -95,16 +102,17 @@ end tell"#, .spawn(); } -fn run_in_iterm(script_path: &Path) { +fn run_in_iterm(script_path: &Path, shell: &str) { let path_str = script_path.to_string_lossy(); let applescript = format!( r#"tell application "iTerm" create window with default profile tell current session of current window - write text "clear; bash {}" + write text "clear; {} {}" end tell activate end tell"#, + shell, escape_applescript(&path_str) ); @@ -114,7 +122,7 @@ end tell"#, .spawn(); } -pub fn edit_file(path: &Path, terminal: &Terminal) { +pub fn edit_file(path: &Path, terminal: &Terminal, shell: &str) { let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string()); match terminal { @@ -124,13 +132,14 @@ pub fn edit_file(path: &Path, terminal: &Terminal) { .arg("Ghostty") .arg("--args") .arg("-e") - .arg("bash") + .arg(shell) .arg("-c") .arg(format!( - "cd {} && {} {}; exec bash", + "cd {} && {} {}; exec {}", escape_shell(&std::env::current_dir().unwrap_or_default().to_string_lossy()), escape_shell(&editor), - escape_shell(&path.to_string_lossy()) + escape_shell(&path.to_string_lossy()), + shell )) .spawn(); } diff --git a/src/config.rs b/src/config.rs index eef3915..a3cfd13 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,11 @@ use std::fs; -use std::path::Path; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[derive(Debug, Clone)] pub struct Config { pub data_dir: PathBuf, pub terminal: Terminal, + pub shell: String, } #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -30,8 +30,12 @@ impl Terminal { impl Config { pub fn new(data_dir: Option) -> Self { let data_dir = data_dir.unwrap_or_else(default_data_dir); - let terminal = load_terminal_preference(&data_dir); - Config { data_dir, terminal } + let (terminal, shell) = load_preferences(&data_dir); + Config { + data_dir, + terminal, + shell, + } } } @@ -41,19 +45,39 @@ fn default_data_dir() -> PathBuf { .unwrap_or_else(|| PathBuf::from(".lines")) } -fn load_terminal_preference(data_dir: &Path) -> Terminal { +fn default_shell() -> String { + std::env::var("SHELL") + .ok() + .and_then(|s| { + Path::new(&s) + .file_name() + .and_then(|n| n.to_str()) + .map(String::from) + }) + .unwrap_or_else(|| "zsh".to_string()) +} + +fn load_preferences(data_dir: &Path) -> (Terminal, String) { let config_path = data_dir.join("config.toml"); let content = match fs::read_to_string(&config_path) { Ok(c) => c, - Err(_) => return Terminal::default(), + Err(_) => return (Terminal::default(), default_shell()), }; match toml::from_str::(&content) { - Ok(value) => value - .get("terminal") - .and_then(|v| v.as_str()) - .and_then(Terminal::from_str) - .unwrap_or_default(), - Err(_) => Terminal::default(), + Ok(value) => { + let terminal = value + .get("terminal") + .and_then(|v| v.as_str()) + .and_then(Terminal::from_str) + .unwrap_or_default(); + let shell = value + .get("shell") + .and_then(|v| v.as_str()) + .map(String::from) + .unwrap_or_else(default_shell); + (terminal, shell) + } + Err(_) => (Terminal::default(), default_shell()), } } diff --git a/src/events.rs b/src/events.rs index 7a2360c..8476146 100644 --- a/src/events.rs +++ b/src/events.rs @@ -38,12 +38,12 @@ pub fn handle_event(app: &mut App, event: AppEvent) -> bool { KeyCode::Char('r') => app.reload(), KeyCode::Char('e') => { if let Some(file) = app.current_file() { - edit_file(&file.path, &app.terminal); + edit_file(&file.path, &app.terminal, &app.shell); } } KeyCode::Enter => { if let (Some(section), Some(file)) = (app.current_section(), app.current_file()) { - run_section(section, file, &app.terminal, &app.data_dir); + run_section(section, file, &app.terminal, &app.shell, &app.data_dir); } } _ => {} diff --git a/src/main.rs b/src/main.rs index 0e5d4ae..d3256be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,7 @@ fn main() -> io::Result<()> { eprintln!("Nepodařilo se vytvořit datovou složku {}: {}", config.data_dir.display(), e); } - let app = App::new(config.data_dir, config.terminal); + let app = App::new(config.data_dir, config.terminal, config.shell); enable_raw_mode()?; let mut stdout = io::stdout();