1use {
2 crate::{
3 app::AppContext,
4 display::{
5 DisplayableTree,
6 Screen,
7 W,
8 },
9 errors::ProgramError,
10 skin::{
11 ExtColorMap,
12 StyleMap,
13 },
14 tree::Tree,
15 },
16 crokey::crossterm::{
17 QueueableCommand,
18 cursor,
19 event::{
20 DisableMouseCapture,
21 EnableMouseCapture,
22 },
23 terminal::{
24 self,
25 EnterAlternateScreen,
26 LeaveAlternateScreen,
27 },
28 },
29 opener,
30 std::{
31 env,
32 io::{
33 self,
34 Write,
35 },
36 path::PathBuf,
37 path::Path,
38 process::Command,
39 },
40 which::which,
41};
42
43#[derive(Debug)]
46pub enum Launchable {
47 Printer { to_print: String },
49
50 TreePrinter {
52 tree: Box<Tree>,
53 skin: Box<StyleMap>,
54 ext_colors: ExtColorMap,
55 width: u16,
56 height: u16,
57 },
58
59 Program {
61 exe: String,
62 args: Vec<String>,
63 working_dir: Option<PathBuf>,
64 switch_terminal: bool,
65 capture_mouse: bool,
66 keyboard_enhanced: bool,
67 },
68
69 SystemOpen { path: PathBuf },
71}
72
73fn resolve_env_variables(parts: Vec<String>) -> Vec<String> {
76 let mut resolved = Vec::new();
77 for part in parts {
78 if let Some(var_name) = part.strip_prefix('$') {
79 if let Ok(val) = env::var(var_name) {
80 resolved.extend(val.split(' ').map(ToString::to_string));
81 continue;
82 }
83 if var_name == "EDITOR" {
84 debug!("Env var $EDITOR not set, looking at editor command for fallback");
85 if let Ok(editor) = which("editor") {
86 if let Some(editor) = editor.to_str() {
87 debug!("Using editor solved as {editor:?}");
88 resolved.push(editor.to_string());
89 continue;
90 }
91 }
92 }
93 }
94 resolved.push(part);
95 }
96 resolved
97}
98
99impl Launchable {
100 pub fn opener(path: PathBuf) -> Launchable {
101 Launchable::SystemOpen { path }
102 }
103 pub fn printer(to_print: String) -> Launchable {
104 Launchable::Printer { to_print }
105 }
106 pub fn tree_printer(
107 tree: &Tree,
108 screen: Screen,
109 style_map: StyleMap,
110 ext_colors: ExtColorMap,
111 ) -> Launchable {
112 Launchable::TreePrinter {
113 tree: Box::new(tree.clone()),
114 skin: Box::new(style_map),
115 ext_colors,
116 width: screen.width,
117 height: (tree.lines.len() as u16).min(screen.height - 1),
118 }
119 }
120
121 pub fn program(
122 parts: Vec<String>,
123 working_dir: Option<PathBuf>,
124 switch_terminal: bool,
125 con: &AppContext,
126 ) -> io::Result<Launchable> {
127 let mut parts = resolve_env_variables(parts).into_iter();
128 match parts.next() {
129 Some(exe) => Ok(Launchable::Program {
130 exe,
131 args: parts.collect(),
132 working_dir,
133 switch_terminal,
134 capture_mouse: con.capture_mouse,
135 keyboard_enhanced: con.keyboard_enhanced,
136 }),
137 None => Err(io::Error::other("Empty launch string")),
138 }
139 }
140
141 pub fn execute(
142 &self,
143 mut w: Option<&mut W>,
144 ) -> Result<(), ProgramError> {
145 match self {
146 Launchable::Printer { to_print } => {
147 println!("{to_print}");
148 Ok(())
149 }
150 Launchable::TreePrinter {
151 tree,
152 skin,
153 ext_colors,
154 width,
155 height,
156 } => {
157 let dp = DisplayableTree::out_of_app(tree, skin, ext_colors, *width, *height);
158 dp.write_on(&mut std::io::stdout())
159 }
160 Launchable::Program {
161 working_dir,
162 switch_terminal,
163 exe,
164 args,
165 capture_mouse,
166 keyboard_enhanced,
167 } => {
168 debug!("working_dir: {working_dir:?}");
169 debug!("switch_terminal: {switch_terminal:?}");
170 if *switch_terminal {
171 if let Some(ref mut w) = &mut w {
175 if *keyboard_enhanced {
176 crokey::pop_keyboard_enhancement_flags()?;
177 }
178 w.queue(cursor::Show)?;
179 w.queue(LeaveAlternateScreen)?;
180 if *capture_mouse {
181 w.queue(DisableMouseCapture)?;
182 }
183 terminal::disable_raw_mode()?;
184 w.flush()?;
185 }
186 }
187 let mut old_working_dir = None;
188 if let Some(working_dir) = working_dir {
189 old_working_dir = std::env::current_dir().ok();
190 if !try_set_current_dir(working_dir) {
191 warn!("Unable to set working dir to {working_dir:?}");
192 old_working_dir = None;
193 }
194 }
195 let exec_res = Command::new(exe)
196 .args(args.iter())
197 .spawn()
198 .and_then(|mut p| p.wait())
199 .map_err(|source| ProgramError::LaunchError {
200 program: exe.clone(),
201 source,
202 });
203 if *switch_terminal {
204 if let Some(ref mut w) = &mut w {
205 terminal::enable_raw_mode()?;
206 if *capture_mouse {
207 w.queue(EnableMouseCapture)?;
208 }
209 w.queue(EnterAlternateScreen)?;
210 w.queue(cursor::Hide)?;
211 w.flush()?;
212 if *keyboard_enhanced {
213 crokey::push_keyboard_enhancement_flags()?;
214 }
215 }
216 }
217 if let Some(old_working_dir) = old_working_dir {
218 if !try_set_current_dir(&old_working_dir) {
219 warn!("Unable to restore working dir to {old_working_dir:?}");
220 }
221 }
222 exec_res?; Ok(())
224 }
225 Launchable::SystemOpen { path } => {
226 opener::open(path)?;
227 Ok(())
228 }
229 }
230 }
231}
232
233pub fn try_set_current_dir(mut dir: &Path) -> bool {
236 loop {
237 if std::env::set_current_dir(dir).is_ok() {
238 debug!("Working dir set to {dir:?}");
239 return true;
240 }
241 let Some(parent_dir) = dir.parent() else {
242 return false;
243 };
244 dir = parent_dir;
245 }
246}