vtcode_core/tools/ripgrep_installer/
mod.rs1mod platform;
7mod state;
8
9use crate::tools::ripgrep_binary::RIPGREP_INSTALL_COMMAND;
10use anyhow::{Result, anyhow};
11use std::process::Command;
12
13use self::platform::install_with_smart_detection;
14use self::state::{InstallLockGuard, InstallationCache};
15
16#[derive(Debug, Clone, PartialEq)]
18pub enum RipgrepStatus {
19 Available { version: String },
21 NotFound,
23 Error { reason: String },
25}
26
27impl RipgrepStatus {
28 pub fn check() -> Self {
30 debug_log("Checking ripgrep availability...");
31 match Command::new("rg").arg("--version").output() {
32 Ok(output) => {
33 if output.status.success() {
34 let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
35 if version.is_empty() {
36 debug_log("ripgrep found but returned empty version");
37 RipgrepStatus::Error {
38 reason: "rg --version returned empty output".to_string(),
39 }
40 } else {
41 debug_log(&format!("ripgrep available: {}", version));
42 RipgrepStatus::Available { version }
43 }
44 } else {
45 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
46 debug_log(&format!("ripgrep check failed: {}", stderr));
47 RipgrepStatus::Error {
48 reason: if stderr.is_empty() {
49 "rg exited with error".to_string()
50 } else {
51 stderr
52 },
53 }
54 }
55 }
56 Err(err) => {
57 if err.kind() == std::io::ErrorKind::NotFound {
58 debug_log("ripgrep not found in PATH");
59 RipgrepStatus::NotFound
60 } else {
61 debug_log(&format!("ripgrep check error: {}", err));
62 RipgrepStatus::Error {
63 reason: err.to_string(),
64 }
65 }
66 }
67 }
68 }
69
70 pub fn install() -> Result<()> {
73 debug_log("Installation attempt started");
74
75 if matches!(Self::check(), RipgrepStatus::Available { .. }) {
76 debug_log("ripgrep already available; skipping installation");
77 return Ok(());
78 }
79
80 if std::env::var("VTCODE_RIPGREP_NO_INSTALL").is_ok() {
81 debug_log("Auto-install disabled via VTCODE_RIPGREP_NO_INSTALL");
82 return Err(anyhow!(
83 "Ripgrep auto-installation disabled via VTCODE_RIPGREP_NO_INSTALL"
84 ));
85 }
86
87 if InstallLockGuard::is_install_in_progress() {
88 debug_log("Another installation is already in progress, skipping");
89 return Err(anyhow!("Ripgrep installation already in progress"));
90 }
91
92 let _lock = InstallLockGuard::acquire()?;
93 debug_log("Installation lock acquired");
94
95 if !InstallationCache::is_stale()
96 && let Ok(cache) = InstallationCache::load()
97 && cache.status == "failed"
98 {
99 let reason = cache.failure_reason.as_deref().unwrap_or("unknown reason");
100 debug_log(&format!("Cache shows previous failure: {}", reason));
101 return Err(anyhow!(
102 "Previous installation attempt failed ({}). Not retrying for 24 hours.",
103 reason
104 ));
105 }
106
107 let result = install_with_smart_detection();
108
109 match result {
110 Ok(()) => match Self::check() {
111 RipgrepStatus::Available { .. } => {
112 debug_log("Installation verified successfully");
113 InstallationCache::mark_success("auto-detected");
114 Ok(())
115 }
116 status => {
117 let msg = format!("Installation verification failed: {:?}", status);
118 debug_log(&msg);
119 InstallationCache::mark_failed("auto-detected", &msg);
120 Err(anyhow!(msg))
121 }
122 },
123 Err(err) => {
124 let msg = err.to_string();
125 debug_log(&format!("Installation failed: {}", msg));
126 InstallationCache::mark_failed("all-methods", &msg);
127 Err(anyhow!(msg))
128 }
129 }
130 }
131
132 pub fn ensure_available() -> Result<Self> {
134 match Self::check() {
135 status @ RipgrepStatus::Available { .. } => Ok(status),
136 RipgrepStatus::NotFound => {
137 tracing::warn!("ripgrep not found, run `{RIPGREP_INSTALL_COMMAND}` to install");
138 Self::install()?;
139 Ok(Self::check())
140 }
141 status => Ok(status),
142 }
143 }
144}
145
146pub(super) fn debug_log(message: &str) {
147 if std::env::var("VTCODE_DEBUG_RIPGREP").is_ok() {
148 tracing::debug!(message, "ripgrep");
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_ripgrep_status_check() {
158 let status = RipgrepStatus::check();
159 tracing::debug!(?status, "Ripgrep status");
160 }
161}