1use crate::core::error::{Error, Result};
4use directories::ProjectDirs;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(default)]
12pub struct Config {
13 pub general: GeneralConfig,
14 pub watch: WatchConfig,
15 pub ignore: IgnoreConfig,
16 pub index: IndexConfig,
17 pub cache: CacheConfig,
18 #[serde(default)]
19 pub projects: HashMap<String, ProjectConfig>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(default)]
24pub struct GeneralConfig {
25 pub default_limit: usize,
27 pub daemon_autostart: bool,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(default)]
33pub struct WatchConfig {
34 pub paths: Vec<PathBuf>,
36 pub recursive: bool,
38 pub debounce_ms: u64,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(default)]
44pub struct IgnoreConfig {
45 pub patterns: Vec<String>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(default)]
51pub struct IndexConfig {
52 pub max_file_size: u64,
54 pub max_files: usize,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(default)]
60pub struct CacheConfig {
61 pub query_ttl: u64,
63 pub max_queries: usize,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, Default)]
68pub struct ProjectConfig {
69 pub ignore: Vec<String>,
71}
72
73impl Default for Config {
74 fn default() -> Self {
75 Self {
76 general: GeneralConfig::default(),
77 watch: WatchConfig::default(),
78 ignore: IgnoreConfig::default(),
79 index: IndexConfig::default(),
80 cache: CacheConfig::default(),
81 projects: HashMap::new(),
82 }
83 }
84}
85
86impl Default for GeneralConfig {
87 fn default() -> Self {
88 Self {
89 default_limit: 20,
90 daemon_autostart: false,
91 }
92 }
93}
94
95impl Default for WatchConfig {
96 fn default() -> Self {
97 Self {
98 paths: vec![],
99 recursive: true,
100 debounce_ms: 100,
101 }
102 }
103}
104
105impl Default for IgnoreConfig {
106 fn default() -> Self {
107 Self {
108 patterns: vec![
109 "node_modules".to_string(),
110 ".git".to_string(),
111 "dist".to_string(),
112 "build".to_string(),
113 "__pycache__".to_string(),
114 "*.min.js".to_string(),
115 "*.map".to_string(),
116 ],
117 }
118 }
119}
120
121impl Default for IndexConfig {
122 fn default() -> Self {
123 Self {
124 max_file_size: 1_048_576, max_files: 100_000,
126 }
127 }
128}
129
130impl Default for CacheConfig {
131 fn default() -> Self {
132 Self {
133 query_ttl: 60,
134 max_queries: 1000,
135 }
136 }
137}
138
139impl Config {
140 pub fn load() -> Result<Self> {
142 let config_path = Self::config_path()?;
143
144 if config_path.exists() {
145 let content = std::fs::read_to_string(&config_path)?;
146 let config: Config = toml::from_str(&content)?;
147 Ok(config)
148 } else {
149 Ok(Config::default())
150 }
151 }
152
153 pub fn config_path() -> Result<PathBuf> {
155 let home = Self::greppy_home()?;
156 Ok(home.join("config.toml"))
157 }
158
159 pub fn greppy_home() -> Result<PathBuf> {
161 if let Ok(home) = std::env::var("GREPPY_HOME") {
163 return Ok(PathBuf::from(home));
164 }
165
166 ProjectDirs::from("dev", "greppy", "greppy")
168 .map(|dirs| dirs.data_dir().to_path_buf())
169 .ok_or_else(|| Error::ConfigError {
170 message: "Could not determine greppy home directory".to_string(),
171 })
172 }
173
174 pub fn index_dir(project_path: &std::path::Path) -> Result<PathBuf> {
176 let home = Self::greppy_home()?;
177 let hash = xxhash_rust::xxh3::xxh3_64(project_path.to_string_lossy().as_bytes());
178 Ok(home.join("indexes").join(format!("{:016x}", hash)))
179 }
180
181 pub fn registry_path() -> Result<PathBuf> {
183 Ok(Self::greppy_home()?.join("registry.json"))
184 }
185
186 pub fn ensure_home() -> Result<()> {
188 let home = Self::greppy_home()?;
189 if !home.exists() {
190 std::fs::create_dir_all(&home)?;
191 }
192 Ok(())
193 }
194
195 pub fn socket_path() -> Result<PathBuf> {
197 if let Ok(socket) = std::env::var("GREPPY_DAEMON_SOCKET") {
198 return Ok(PathBuf::from(socket));
199 }
200 let home = Self::greppy_home()?;
201 Ok(home.join("daemon.sock"))
202 }
203
204 pub fn pid_path() -> Result<PathBuf> {
206 Ok(Self::greppy_home()?.join("daemon.pid"))
207 }
208
209 #[cfg(windows)]
211 pub fn daemon_port() -> u16 {
212 std::env::var("GREPPY_DAEMON_PORT")
213 .ok()
214 .and_then(|p| p.parse().ok())
215 .unwrap_or(DEFAULT_DAEMON_PORT)
216 }
217
218 #[cfg(windows)]
220 pub fn port_path() -> Result<PathBuf> {
221 Ok(Self::greppy_home()?.join("daemon.port"))
222 }
223}
224
225#[cfg(windows)]
227const DEFAULT_DAEMON_PORT: u16 = 19532;
228
229pub const MAX_FILE_SIZE: u64 = 1_048_576; pub const CHUNK_MAX_LINES: usize = 50;
231pub const CHUNK_OVERLAP: usize = 5;