1- use crate :: db:: schema:: init_db ;
1+ use crate :: db:: schema:: init_db_lightweight ;
22use crate :: graph:: GraphEngine ;
33use crate :: indexer:: { reindex_file_sync, ParserManager } ;
4- use crate :: watcher:: { FileChange , FileChangeKind } ;
4+ use crate :: watcher:: FileChange ;
5+ use std:: collections:: HashSet ;
56use std:: path:: { Path , PathBuf } ;
67use tokio:: sync:: mpsc;
78
8- pub async fn handle_file_change ( db_path : & Path , change : FileChange ) {
9- let db = match init_db ( db_path) {
10- Ok ( db) => db,
11- Err ( e) => {
12- tracing:: error!( "Failed to init db: {}" , e) ;
13- return ;
14- }
15- } ;
16- let graph = GraphEngine :: new ( db) ;
17- let mut parser = ParserManager :: new ( ) ;
18- let _ = parser. init_parsers ( ) ;
9+ const IGNORED_PATH_SEGMENTS : & [ & str ] = & [
10+ ".git" ,
11+ ".leankg" ,
12+ "node_modules" ,
13+ "vendor" ,
14+ "target" ,
15+ "__pycache__" ,
16+ ".DS_Store" ,
17+ ".gradle" ,
18+ ".idea" ,
19+ ".vscode" ,
20+ ] ;
1921
20- let path_str = change. path . to_string_lossy ( ) ;
21- if path_str. contains ( "node_modules" ) || path_str. contains ( "vendor" ) || path_str. contains ( ".git" )
22- {
23- return ;
24- }
22+ const IGNORED_EXTENSIONS : & [ & str ] = & [
23+ ".db" ,
24+ ".db-wal" ,
25+ ".db-shm" ,
26+ ".db-journal" ,
27+ ".sqlite" ,
28+ ".sqlite-wal" ,
29+ ".sqlite-shm" ,
30+ ".lock" ,
31+ ".log" ,
32+ ".pid" ,
33+ ] ;
2534
26- match change. kind {
27- FileChangeKind :: Modified | FileChangeKind :: Created => {
28- match reindex_file_sync ( & graph, & mut parser, & path_str) {
29- Ok ( count) => {
30- if count > 0 {
31- tracing:: info!( "Indexed {} elements from {}" , count, path_str) ;
32- }
33- }
34- Err ( e) => {
35- tracing:: warn!( "Failed to index {}: {}" , path_str, e) ;
36- }
37- }
35+ fn should_ignore ( path : & Path ) -> bool {
36+ let path_str = path. to_string_lossy ( ) ;
37+
38+ for segment in IGNORED_PATH_SEGMENTS {
39+ if path_str. contains ( segment) {
40+ return true ;
3841 }
39- FileChangeKind :: Deleted => {
40- if let Err ( e) = graph. remove_elements_by_file ( & path_str) {
41- tracing:: warn!( "Failed to remove file from index: {}" , e) ;
42- }
42+ }
43+
44+ if let Some ( ext) = path. extension ( ) . and_then ( |e| e. to_str ( ) ) {
45+ let ext_with_dot = format ! ( ".{}" , ext. to_lowercase( ) ) ;
46+ if IGNORED_EXTENSIONS . contains ( & ext_with_dot. as_str ( ) ) {
47+ return true ;
4348 }
4449 }
50+
51+ false
4552}
4653
4754pub async fn start_watcher ( db_path : PathBuf , watch_path : PathBuf , _rx : mpsc:: Receiver < FileChange > ) {
@@ -59,20 +66,71 @@ pub async fn start_watcher(db_path: PathBuf, watch_path: PathBuf, _rx: mpsc::Rec
5966 }
6067 } ;
6168
62- let ( tx, watcher_rx ) = mpsc:: channel ( 100 ) ;
69+ let ( tx, mut rx ) = mpsc:: channel ( 256 ) ;
6370 let async_watcher = watcher. into_async ( tx) ;
64-
6571 tokio:: spawn ( async_watcher. run ( ) ) ;
6672
67- let mut rx = watcher_rx;
68- let db_path_clone = db_path. clone ( ) ;
73+ let db = match init_db_lightweight ( & db_path) {
74+ Ok ( db) => db,
75+ Err ( e) => {
76+ tracing:: error!( "Failed to init db for watcher: {}" , e) ;
77+ return ;
78+ }
79+ } ;
80+ let graph = GraphEngine :: new ( db) ;
81+ let mut parser = ParserManager :: new ( ) ;
82+ if let Err ( e) = parser. init_parsers ( ) {
83+ tracing:: error!( "Failed to init parsers for watcher: {}" , e) ;
84+ return ;
85+ }
86+
87+ let debounce_interval = std:: time:: Duration :: from_millis ( 500 ) ;
88+ let mut pending: HashSet < PathBuf > = HashSet :: new ( ) ;
89+ let mut debounce_timer = tokio:: time:: Instant :: now ( ) + debounce_interval;
6990
7091 loop {
7192 tokio:: select! {
7293 Some ( change) = rx. recv( ) => {
73- handle_file_change( & db_path_clone, change) . await ;
94+ if should_ignore( & change. path) {
95+ continue ;
96+ }
97+
98+ let ext = change. path. extension( )
99+ . and_then( |e| e. to_str( ) )
100+ . map( |e| e. to_lowercase( ) )
101+ . unwrap_or_default( ) ;
102+ let is_source = [
103+ "rs" , "go" , "ts" , "tsx" , "js" , "jsx" , "py" , "java" ,
104+ "kt" , "kts" , "c" , "cpp" , "h" , "hpp" , "cs" , "rb" ,
105+ "swift" , "scala" , "clj" , "hs" , "zig" , "nim" ,
106+ "tf" , "proto" , "graphql" , "toml" , "yaml" , "yml" ,
107+ "md" , "rst" ,
108+ ] . contains( & ext. as_str( ) ) ;
109+
110+ if !is_source {
111+ continue ;
112+ }
113+
114+ pending. insert( change. path) ;
115+ debounce_timer = tokio:: time:: Instant :: now( ) + debounce_interval;
116+ }
117+ _ = tokio:: time:: sleep_until( debounce_timer) , if !pending. is_empty( ) => {
118+ let files: Vec <PathBuf > = pending. drain( ) . collect( ) ;
119+ for file_path in files {
120+ let path_str = file_path. to_string_lossy( ) ;
121+ match reindex_file_sync( & graph, & mut parser, & path_str) {
122+ Ok ( count) => {
123+ if count > 0 {
124+ tracing:: info!( "Indexed {} elements from {}" , count, path_str) ;
125+ }
126+ }
127+ Err ( e) => {
128+ tracing:: warn!( "Failed to index {}: {}" , path_str, e) ;
129+ }
130+ }
131+ }
74132 }
75- _ = tokio:: time:: sleep( std:: time:: Duration :: from_secs( 60 ) ) => {
133+ _ = tokio:: time:: sleep( std:: time:: Duration :: from_secs( 60 ) ) , if pending . is_empty ( ) => {
76134 tracing:: debug!( "Watcher still running for {}" , watch_path. display( ) ) ;
77135 }
78136 }
0 commit comments