1mod error;
9
10pub mod cache;
11pub mod client;
12pub mod config;
13pub mod context;
14pub mod daemon;
15pub(crate) mod fsutil;
16pub mod git;
17pub mod indexer;
18pub mod io_backend;
19pub mod lang;
20pub mod meta;
21pub mod paths;
22pub mod proto;
23pub mod resolve;
24pub mod savings;
25pub mod search;
26pub mod segment;
27#[cfg(feature = "semantic")]
28pub mod semantic;
29pub mod structural;
30pub mod symbol;
31pub mod trigram;
32pub mod walk;
33pub mod watch;
34
35pub use error::{Error, Result};
36
37use std::path::{Path, PathBuf};
38
39use config::Config;
40use indexer::{IndexStats, Indexer};
41use io_backend::IoBackend;
42use meta::Meta;
43use paths::Paths;
44use search::Searcher;
45
46#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
48pub struct Status {
49 pub root: PathBuf,
50 pub indexed: bool,
51 pub segments: usize,
52 pub doc_count: u64,
53 pub symbol_count: u64,
54 pub last_indexed: u64,
55 pub backend: String,
56}
57
58pub struct Greplm {
60 paths: Paths,
61 config: Config,
62 backend: Box<dyn IoBackend>,
63}
64
65impl Greplm {
66 pub fn open(root: impl AsRef<Path>) -> Result<Greplm> {
68 let paths = Paths::new(root);
69 let config = Config::load(&paths.config_file())?;
70 Ok(Greplm {
71 paths,
72 config,
73 backend: io_backend::default_backend(),
74 })
75 }
76
77 pub fn discover(start: impl AsRef<Path>) -> Result<Greplm> {
80 let start = start.as_ref();
81 let mut cur = Some(start);
82 while let Some(dir) = cur {
83 if dir.join(paths::DIR_NAME).is_dir() {
84 return Greplm::open(dir);
85 }
86 cur = dir.parent();
87 }
88 Greplm::open(start)
89 }
90
91 pub fn root(&self) -> &Path {
92 &self.paths.root
93 }
94
95 pub fn config(&self) -> &Config {
96 &self.config
97 }
98
99 pub fn ensure_initialized(&self) -> Result<()> {
101 std::fs::create_dir_all(self.paths.segments_dir())
102 .map_err(|e| Error::io(self.paths.segments_dir(), e))?;
103 let cfg = self.paths.config_file();
104 if !cfg.exists() {
105 self.config.save(&cfg)?;
106 }
107 let gi = self.paths.gitignore_file();
108 if !gi.exists() {
109 std::fs::write(&gi, "*\n").map_err(|e| Error::io(&gi, e))?;
110 }
111 Ok(())
112 }
113
114 pub fn index(&self, force: bool) -> Result<IndexStats> {
116 self.ensure_initialized()?;
117 let indexer = Indexer::new(&self.paths, &self.config, self.backend.as_ref());
118 if force {
119 indexer.index_full()
120 } else {
121 indexer.index_incremental()
122 }
123 }
124
125 pub fn compact(&self) -> Result<IndexStats> {
128 self.ensure_initialized()?;
129 Indexer::new(&self.paths, &self.config, self.backend.as_ref()).compact()
130 }
131
132 pub fn searcher(&self) -> Result<Searcher> {
134 Searcher::open(&self.paths)
135 }
136
137 pub fn status(&self) -> Result<Status> {
139 let meta = if self.paths.meta_file().exists() {
140 Meta::load(&self.paths.meta_file())?
141 } else {
142 Meta::default()
143 };
144 Ok(Status {
145 root: self.paths.root.clone(),
146 indexed: !meta.segments.is_empty(),
147 segments: meta.segments.len(),
148 doc_count: meta.doc_count,
149 symbol_count: meta.symbol_count,
150 last_indexed: meta.last_indexed,
151 backend: self.backend.name().to_string(),
152 })
153 }
154
155 pub fn clean(&self) -> Result<()> {
157 if self.paths.base.is_dir() {
158 std::fs::remove_dir_all(&self.paths.base)
159 .map_err(|e| Error::io(&self.paths.base, e))?;
160 }
161 Ok(())
162 }
163
164 pub fn watch<F: FnMut(&IndexStats)>(
169 &self,
170 debounce: std::time::Duration,
171 on_change: F,
172 ) -> Result<()> {
173 watch::run(self, debounce, on_change)
174 }
175
176 pub fn socket_path(&self) -> PathBuf {
178 self.paths.base.join(proto::SOCKET_NAME)
179 }
180
181 pub fn record_savings(
184 &self,
185 kind: &str,
186 files: &std::collections::BTreeSet<String>,
187 returned_chars: u64,
188 results: u64,
189 ) {
190 savings::record(&self.paths, kind, files, returned_chars, results);
191 }
192
193 pub fn savings_report(&self) -> savings::SavingsReport {
195 savings::report(&self.paths)
196 }
197
198 pub(crate) fn paths(&self) -> &Paths {
199 &self.paths
200 }
201}