Add configurable terminal (Ghostty, iTerm, Terminal) via config.toml
This commit is contained in:
parent
d4150757ca
commit
b167d2c980
7 changed files with 198 additions and 30 deletions
55
Cargo.lock
generated
55
Cargo.lock
generated
|
|
@ -763,6 +763,7 @@ dependencies = [
|
|||
"crossterm",
|
||||
"directories",
|
||||
"ratatui",
|
||||
"toml",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
|
@ -1388,6 +1389,15 @@ dependencies = [
|
|||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
|
|
@ -1627,6 +1637,45 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
|
|
@ -1938,6 +1987,12 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ clap = { version = "4.6.1", features = ["derive"] }
|
|||
crossterm = "0.29.0"
|
||||
directories = "6.0.0"
|
||||
ratatui = "0.30.1"
|
||||
toml = "1.1.2"
|
||||
walkdir = "2.5.0"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::config::Terminal;
|
||||
use crate::parser::{load_groups, CommandFile, Group, Section};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -11,6 +12,7 @@ pub enum Focus {
|
|||
|
||||
pub struct App {
|
||||
pub data_dir: PathBuf,
|
||||
pub terminal: Terminal,
|
||||
pub groups: Vec<Group>,
|
||||
pub group_index: usize,
|
||||
pub file_index: usize,
|
||||
|
|
@ -20,10 +22,11 @@ pub struct App {
|
|||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(data_dir: PathBuf) -> Self {
|
||||
pub fn new(data_dir: PathBuf, terminal: Terminal) -> Self {
|
||||
let groups = load_groups(&data_dir);
|
||||
App {
|
||||
data_dir,
|
||||
terminal,
|
||||
groups,
|
||||
group_index: 0,
|
||||
file_index: 0,
|
||||
|
|
|
|||
118
src/commands.rs
118
src/commands.rs
|
|
@ -3,9 +3,10 @@ use std::os::unix::fs::PermissionsExt;
|
|||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use crate::config::Terminal;
|
||||
use crate::parser::{CommandFile, Section};
|
||||
|
||||
pub fn run_section(section: &Section, file: &CommandFile) {
|
||||
pub fn run_section(section: &Section, file: &CommandFile, terminal: &Terminal) {
|
||||
let commands = if section.is_run_all() {
|
||||
file.sections
|
||||
.iter()
|
||||
|
|
@ -21,14 +22,14 @@ pub fn run_section(section: &Section, file: &CommandFile) {
|
|||
}
|
||||
|
||||
let script = build_script(&commands);
|
||||
execute_in_terminal(&script);
|
||||
execute_in_terminal(&script, terminal);
|
||||
}
|
||||
|
||||
fn build_script(commands: &[String]) -> String {
|
||||
commands.join("\n")
|
||||
}
|
||||
|
||||
fn execute_in_terminal(script: &str) {
|
||||
fn execute_in_terminal(script: &str, terminal: &Terminal) {
|
||||
let tmp_dir = std::env::temp_dir();
|
||||
let script_path = tmp_dir.join(format!("lines_run_{}.sh", random_suffix()));
|
||||
|
||||
|
|
@ -37,22 +38,28 @@ fn execute_in_terminal(script: &str) {
|
|||
Err(_) => return,
|
||||
};
|
||||
|
||||
if file.write_all(script.as_bytes()).is_err() {
|
||||
let script_with_cleanup = format!("{}\nrm -f {}", script, escape_shell(&script_path.to_string_lossy()));
|
||||
|
||||
if file.write_all(script_with_cleanup.as_bytes()).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Best-effort attempt to make executable; Terminal.app can run `bash script.sh` anyway.
|
||||
let _ = std::fs::set_permissions(&script_path, std::fs::Permissions::from_mode(0o755));
|
||||
|
||||
let path_str = script_path.to_string_lossy();
|
||||
match terminal {
|
||||
Terminal::Ghostty => run_in_ghostty(&script_path),
|
||||
Terminal::Iterm => run_in_iterm(&script_path),
|
||||
Terminal::Builtin => run_in_terminal_app(&script_path),
|
||||
}
|
||||
}
|
||||
|
||||
// Open a new Terminal.app window and run the script.
|
||||
fn run_in_terminal_app(script_path: &Path) {
|
||||
let path_str = script_path.to_string_lossy();
|
||||
let applescript = format!(
|
||||
r#"tell application "Terminal"
|
||||
do script "clear; bash {}; rm {}"
|
||||
do script "clear; bash {}"
|
||||
activate
|
||||
end tell"#,
|
||||
escape_applescript(&path_str),
|
||||
escape_applescript(&path_str)
|
||||
);
|
||||
|
||||
|
|
@ -62,29 +69,90 @@ end tell"#,
|
|||
.spawn();
|
||||
}
|
||||
|
||||
fn run_in_ghostty(script_path: &Path) {
|
||||
let path_str = script_path.to_string_lossy();
|
||||
let _ = Command::new("open")
|
||||
.arg("-na")
|
||||
.arg("Ghostty")
|
||||
.arg("--args")
|
||||
.arg("-e")
|
||||
.arg("bash")
|
||||
.arg(path_str.as_ref())
|
||||
.spawn();
|
||||
}
|
||||
|
||||
fn run_in_iterm(script_path: &Path) {
|
||||
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 {}"
|
||||
end tell
|
||||
activate
|
||||
end tell"#,
|
||||
escape_applescript(&path_str)
|
||||
);
|
||||
|
||||
let _ = Command::new("osascript")
|
||||
.arg("-e")
|
||||
.arg(applescript)
|
||||
.spawn();
|
||||
}
|
||||
|
||||
pub fn edit_file(path: &Path, terminal: &Terminal) {
|
||||
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
|
||||
|
||||
match terminal {
|
||||
Terminal::Ghostty => {
|
||||
let _ = Command::new("open")
|
||||
.arg("-na")
|
||||
.arg("Ghostty")
|
||||
.arg("--args")
|
||||
.arg("-e")
|
||||
.arg("bash")
|
||||
.arg("-c")
|
||||
.arg(format!(
|
||||
"cd {} && {} {}; exec bash",
|
||||
escape_shell(&std::env::current_dir().unwrap_or_default().to_string_lossy()),
|
||||
escape_shell(&editor),
|
||||
escape_shell(&path.to_string_lossy())
|
||||
))
|
||||
.spawn();
|
||||
}
|
||||
_ => {
|
||||
let app = match terminal {
|
||||
Terminal::Iterm => "iTerm",
|
||||
_ => "Terminal",
|
||||
};
|
||||
let _ = Command::new("osascript")
|
||||
.arg("-e")
|
||||
.arg(format!(
|
||||
r#"tell application "{}"
|
||||
do script "cd {}; {} {}; exit"
|
||||
activate
|
||||
end tell"#,
|
||||
app,
|
||||
escape_applescript(&std::env::current_dir().unwrap_or_default().to_string_lossy()),
|
||||
escape_applescript(&editor),
|
||||
escape_applescript(&path.to_string_lossy())
|
||||
))
|
||||
.spawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_applescript(s: &str) -> String {
|
||||
s.replace('\\', "\\\\").replace('"', "\\\"")
|
||||
}
|
||||
|
||||
fn escape_shell(s: &str) -> String {
|
||||
s.replace('\\', "\\\\").replace('"', "\\\"").replace('\'', "'\"'\"'")
|
||||
}
|
||||
|
||||
fn random_suffix() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn edit_file(path: &Path) {
|
||||
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
|
||||
let _ = Command::new("osascript")
|
||||
.arg("-e")
|
||||
.arg(format!(
|
||||
r#"tell application "Terminal"
|
||||
do script "cd {}; {} {}; exit"
|
||||
activate
|
||||
end tell"#,
|
||||
escape_applescript(&std::env::current_dir().unwrap_or_default().to_string_lossy()),
|
||||
escape_applescript(&editor),
|
||||
escape_applescript(&path.to_string_lossy())
|
||||
))
|
||||
.spawn();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,37 @@
|
|||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub data_dir: PathBuf,
|
||||
pub terminal: Terminal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub enum Terminal {
|
||||
#[default]
|
||||
Builtin,
|
||||
Ghostty,
|
||||
Iterm,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"terminal" | "terminal.app" => Some(Terminal::Builtin),
|
||||
"ghostty" => Some(Terminal::Ghostty),
|
||||
"iterm" | "iterm2" => Some(Terminal::Iterm),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(data_dir: Option<PathBuf>) -> Self {
|
||||
let data_dir = data_dir.unwrap_or_else(default_data_dir);
|
||||
Config { data_dir }
|
||||
let terminal = load_terminal_preference(&data_dir);
|
||||
Config { data_dir, terminal }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -16,3 +40,20 @@ fn default_data_dir() -> PathBuf {
|
|||
.map(|d| d.home_dir().join(".lines"))
|
||||
.unwrap_or_else(|| PathBuf::from(".lines"))
|
||||
}
|
||||
|
||||
fn load_terminal_preference(data_dir: &Path) -> Terminal {
|
||||
let config_path = data_dir.join("config.toml");
|
||||
let content = match fs::read_to_string(&config_path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Terminal::default(),
|
||||
};
|
||||
|
||||
match toml::from_str::<toml::Value>(&content) {
|
||||
Ok(value) => value
|
||||
.get("terminal")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(Terminal::from_str)
|
||||
.unwrap_or_default(),
|
||||
Err(_) => Terminal::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
edit_file(&file.path, &app.terminal);
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if let (Some(section), Some(file)) = (app.current_section(), app.current_file()) {
|
||||
run_section(section, file);
|
||||
run_section(section, file, &app.terminal);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
let app = App::new(config.data_dir, config.terminal);
|
||||
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue