1use std::io::{self, Write};
9
10use crate::config::ShellType;
11use crate::shader_installer;
12use crate::shell_integration_installer;
13
14pub fn install_shaders_cli(skip_prompt: bool) -> anyhow::Result<()> {
16 let shaders_dir = crate::config::Config::shaders_dir();
17
18 println!("=============================================");
19 println!(" par-term Shader Installer");
20 println!("=============================================");
21 println!();
22 println!("Target directory: {}", shaders_dir.display());
23 println!();
24
25 if shaders_dir.exists() && shader_installer::has_shader_files(&shaders_dir) && !skip_prompt {
27 println!("WARNING: This will overwrite existing shaders in:");
28 println!(" {}", shaders_dir.display());
29 println!();
30 print!("Do you want to continue? [y/N] ");
31 io::stdout().flush()?;
32
33 let mut response = String::new();
34 io::stdin().read_line(&mut response)?;
35 let response = response.trim().to_lowercase();
36
37 if response != "y" && response != "yes" {
38 println!("Installation cancelled.");
39 return Ok(());
40 }
41 println!();
42 }
43
44 println!("Fetching latest release information...");
46
47 const REPO: &str = "paulrobello/par-term";
48 let api_url = format!("https://api.github.com/repos/{}/releases/latest", REPO);
49 let (zip_url, checksum_url) = shader_installer::get_shaders_download_url(&api_url, REPO)
50 .map_err(|e| anyhow::anyhow!(e))?;
51
52 println!("Downloading shaders from: {}", zip_url);
53 println!();
54
55 let zip_data = shader_installer::download_and_verify(&zip_url, checksum_url.as_deref())
57 .map_err(|e| anyhow::anyhow!(e))?;
58
59 std::fs::create_dir_all(&shaders_dir)?;
61
62 println!("Extracting shaders to {}...", shaders_dir.display());
64 shader_installer::extract_shaders(&zip_data, &shaders_dir).map_err(|e| anyhow::anyhow!(e))?;
65
66 let shader_count = shader_installer::count_shader_files(&shaders_dir);
68
69 println!();
70 println!("=============================================");
71 println!(" Installation complete!");
72 println!("=============================================");
73 println!();
74 println!("Installed {} shaders to:", shader_count);
75 println!(" {}", shaders_dir.display());
76 println!();
77 println!("To use a shader, add to your config.yaml:");
78 println!(" custom_shader: \"shader_name.glsl\"");
79 println!(" custom_shader_enabled: true");
80 println!();
81 println!("For cursor shaders:");
82 println!(" cursor_shader: \"cursor_glow.glsl\"");
83 println!(" cursor_shader_enabled: true");
84 println!();
85 println!("See docs/SHADERS.md for the full shader gallery.");
86
87 Ok(())
88}
89
90pub fn install_shell_integration_cli(shell: Option<ShellType>) -> anyhow::Result<()> {
92 let detected = shell_integration_installer::detected_shell();
93 let target_shell = shell.unwrap_or(detected);
94
95 println!("=============================================");
96 println!(" par-term Shell Integration Installer");
97 println!("=============================================");
98 println!();
99
100 if target_shell == ShellType::Unknown {
101 eprintln!("Error: Could not detect shell type.");
102 eprintln!("Please specify your shell with --shell bash|zsh|fish");
103 return Err(anyhow::anyhow!("Unknown shell type"));
104 }
105
106 println!("Detected shell: {:?}", target_shell);
107
108 if shell_integration_installer::is_installed() {
110 println!("Shell integration is already installed.");
111 print!("Do you want to reinstall? [y/N] ");
112 io::stdout().flush()?;
113
114 let mut response = String::new();
115 io::stdin().read_line(&mut response)?;
116 let response = response.trim().to_lowercase();
117
118 if response != "y" && response != "yes" {
119 println!("Installation cancelled.");
120 return Ok(());
121 }
122 println!();
123 }
124
125 println!("Installing shell integration...");
126
127 match shell_integration_installer::install(Some(target_shell)) {
128 Ok(result) => {
129 println!();
130 println!("=============================================");
131 println!(" Installation complete!");
132 println!("=============================================");
133 println!();
134 println!("Script installed to:");
135 println!(" {}", result.script_path.display());
136 println!();
137 println!("Added source line to:");
138 println!(" {}", result.rc_file.display());
139 println!();
140 if result.needs_restart {
141 println!("Please restart your shell or run:");
142 println!(" source {}", result.rc_file.display());
143 }
144 Ok(())
145 }
146 Err(e) => {
147 eprintln!("Error: {}", e);
148 Err(anyhow::anyhow!(e))
149 }
150 }
151}
152
153pub fn uninstall_shell_integration_cli() -> anyhow::Result<()> {
155 println!("=============================================");
156 println!(" par-term Shell Integration Uninstaller");
157 println!("=============================================");
158 println!();
159
160 if !shell_integration_installer::is_installed() {
161 println!("Shell integration is not installed.");
162 return Ok(());
163 }
164
165 println!("Uninstalling shell integration...");
166
167 match shell_integration_installer::uninstall() {
168 Ok(result) => {
169 println!();
170 println!("=============================================");
171 println!(" Uninstallation complete!");
172 println!("=============================================");
173 println!();
174
175 if !result.cleaned.is_empty() {
176 println!("Cleaned RC files:");
177 for path in &result.cleaned {
178 println!(" {}", path.display());
179 }
180 println!();
181 }
182
183 if !result.scripts_removed.is_empty() {
184 println!("Removed integration scripts:");
185 for path in &result.scripts_removed {
186 println!(" {}", path.display());
187 }
188 println!();
189 }
190
191 if !result.needs_manual.is_empty() {
192 println!("WARNING: Some files need manual cleanup:");
193 for path in &result.needs_manual {
194 println!(" {}", path.display());
195 }
196 println!();
197 }
198
199 Ok(())
200 }
201 Err(e) => {
202 eprintln!("Error: {}", e);
203 Err(anyhow::anyhow!(e))
204 }
205 }
206}
207
208pub fn uninstall_shaders_cli(force: bool) -> anyhow::Result<()> {
210 let shaders_dir = crate::config::Config::shaders_dir();
211
212 println!("=============================================");
213 println!(" par-term Shader Uninstaller");
214 println!("=============================================");
215 println!();
216 println!("Shaders directory: {}", shaders_dir.display());
217 println!();
218
219 if !shaders_dir.exists() {
220 println!("No shaders installed.");
221 return Ok(());
222 }
223
224 let manifest_path = shaders_dir.join("manifest.json");
226 if !manifest_path.exists() {
227 println!("No manifest.json found. Cannot determine which files are bundled.");
228 println!("Only files installed with the installer can be safely uninstalled.");
229 return Err(anyhow::anyhow!("No manifest found"));
230 }
231
232 if !force {
233 println!("This will remove bundled shader files.");
234 println!("User-created and modified files will be preserved.");
235 println!();
236 print!("Do you want to continue? [y/N] ");
237 io::stdout().flush()?;
238
239 let mut response = String::new();
240 io::stdin().read_line(&mut response)?;
241 let response = response.trim().to_lowercase();
242
243 if response != "y" && response != "yes" {
244 println!("Uninstallation cancelled.");
245 return Ok(());
246 }
247 println!();
248 }
249
250 println!("Uninstalling shaders...");
251
252 match shader_installer::uninstall_shaders(force) {
253 Ok(result) => {
254 println!();
255 println!("=============================================");
256 println!(" Uninstallation complete!");
257 println!("=============================================");
258 println!();
259 println!("Removed {} bundled files.", result.removed);
260
261 if result.kept > 0 {
262 println!("Preserved {} user files.", result.kept);
263 }
264
265 if !result.needs_confirmation.is_empty() {
266 println!();
267 println!("Modified files that were preserved:");
268 for path in &result.needs_confirmation {
269 println!(" {}", path);
270 }
271 }
272
273 Ok(())
274 }
275 Err(e) => {
276 eprintln!("Error: {}", e);
277 Err(anyhow::anyhow!(e))
278 }
279 }
280}
281
282pub fn self_update_cli(skip_prompt: bool) -> anyhow::Result<()> {
284 use par_term_update::self_updater;
285 use par_term_update::update_checker;
286
287 println!("=============================================");
288 println!(" par-term Self-Updater");
289 println!("=============================================");
290 println!();
291
292 let current_version = env!("CARGO_PKG_VERSION");
293 println!("Current version: {}", current_version);
294
295 let installation = self_updater::detect_installation().map_err(|e| anyhow::anyhow!(e))?;
297 println!("Installation type: {}", installation.description());
298 println!();
299
300 match &installation {
302 self_updater::InstallationType::Homebrew => {
303 println!("par-term is installed via Homebrew.");
304 println!("Please update with:");
305 println!(" brew upgrade --cask par-term");
306 return Err(anyhow::anyhow!("Cannot self-update Homebrew installation"));
307 }
308 self_updater::InstallationType::CargoInstall => {
309 println!("par-term is installed via cargo.");
310 println!("Please update with:");
311 println!(" cargo install par-term");
312 return Err(anyhow::anyhow!("Cannot self-update cargo installation"));
313 }
314 _ => {}
315 }
316
317 println!("Checking for updates...");
319 let release_info = update_checker::fetch_latest_release().map_err(|e| anyhow::anyhow!(e))?;
320
321 let latest_version = release_info
322 .version
323 .strip_prefix('v')
324 .unwrap_or(&release_info.version);
325
326 let current = semver::Version::parse(current_version)?;
327 let latest = semver::Version::parse(latest_version)?;
328
329 if latest <= current {
330 println!();
331 println!(
332 "You are already running the latest version ({}).",
333 current_version
334 );
335 return Ok(());
336 }
337
338 println!();
339 println!(
340 "New version available: {} -> {}",
341 current_version, latest_version
342 );
343 if let Some(ref notes) = release_info.release_notes {
344 println!();
345 println!("Release notes:");
346 for line in notes.lines().take(10) {
348 println!(" {}", line);
349 }
350 if notes.lines().count() > 10 {
351 println!(" ...");
352 }
353 }
354 println!();
355
356 if !skip_prompt {
358 print!("Do you want to update? [y/N] ");
359 io::stdout().flush()?;
360
361 let mut response = String::new();
362 io::stdin().read_line(&mut response)?;
363 let response = response.trim().to_lowercase();
364
365 if response != "y" && response != "yes" {
366 println!("Update cancelled.");
367 return Ok(());
368 }
369 println!();
370 }
371
372 println!("Downloading and installing update...");
373
374 match self_updater::perform_update(latest_version, crate::VERSION) {
375 Ok(result) => {
376 println!();
377 println!("=============================================");
378 println!(" Update complete!");
379 println!("=============================================");
380 println!();
381 println!("Updated: {} -> {}", result.old_version, result.new_version);
382 println!("Location: {}", result.install_path.display());
383 if result.needs_restart {
384 println!();
385 println!("Please restart par-term to use the new version.");
386 }
387 Ok(())
388 }
389 Err(e) => {
390 eprintln!("Update failed: {}", e);
391 Err(anyhow::anyhow!(e))
392 }
393 }
394}
395
396pub fn install_integrations_cli(skip_prompt: bool) -> anyhow::Result<()> {
398 println!("=============================================");
399 println!(" par-term Integrations Installer");
400 println!("=============================================");
401 println!();
402 println!("This will install:");
403 println!(" 1. Shader collection from latest release");
404 println!(" 2. Shell integration for your current shell");
405 println!();
406
407 if !skip_prompt {
408 print!("Do you want to continue? [y/N] ");
409 io::stdout().flush()?;
410
411 let mut response = String::new();
412 io::stdin().read_line(&mut response)?;
413 let response = response.trim().to_lowercase();
414
415 if response != "y" && response != "yes" {
416 println!("Installation cancelled.");
417 return Ok(());
418 }
419 println!();
420 }
421
422 println!("Step 1: Installing shaders...");
424 println!("---------------------------------------------");
425
426 let shader_result = install_shaders_cli(true);
427 if shader_result.is_err() {
428 println!();
429 println!("WARNING: Shader installation failed.");
430 println!("Continuing with shell integration...");
431 }
432
433 println!();
434 println!("Step 2: Installing shell integration...");
435 println!("---------------------------------------------");
436
437 let shell_result = install_shell_integration_cli(None);
438
439 println!();
440 println!("=============================================");
441 println!(" Integrations Installation Summary");
442 println!("=============================================");
443 println!();
444
445 match (&shader_result, &shell_result) {
446 (Ok(()), Ok(())) => {
447 println!("All integrations installed successfully!");
448 }
449 (Err(_), Ok(())) => {
450 println!("Shell integration: INSTALLED");
451 println!("Shaders: FAILED (see above for errors)");
452 }
453 (Ok(()), Err(_)) => {
454 println!("Shaders: INSTALLED");
455 println!("Shell integration: FAILED (see above for errors)");
456 }
457 (Err(_), Err(_)) => {
458 println!("Both installations failed. See above for errors.");
459 }
460 }
461
462 if shader_result.is_ok() || shell_result.is_ok() {
464 Ok(())
465 } else {
466 Err(anyhow::anyhow!("Both installations failed"))
467 }
468}