Skip to content

Commit 8acf3b0

Browse files
committed
detect file changes
1 parent 9902a25 commit 8acf3b0

6 files changed

Lines changed: 196 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 100 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@ rfd = "0.15.3"
3838
open = "5"
3939
shellexpand = "3.1.0"
4040
chrono = "0.4.40"
41+
notify = "8.0.0"
42+
notify-debouncer-mini = "0.6.0"

src/lsp/change_notifier.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use std::{path::PathBuf, sync::Arc, time::Duration};
2+
3+
use anyhow::Result;
4+
use async_lsp::{LanguageServer, ServerSocket};
5+
use lsp_types::{DidChangeWatchedFilesParams, FileChangeType, FileEvent};
6+
use notify_debouncer_mini::{
7+
DebounceEventResult, DebouncedEvent, Debouncer, new_debouncer, notify::*,
8+
};
9+
use tokio::{runtime::Handle, sync::Mutex};
10+
use url::Url;
11+
12+
use crate::project::Project;
13+
14+
#[derive(Debug)]
15+
pub struct ChangeNotifier {
16+
#[allow(dead_code)] // Keep the handle to ensure the change notifier runs
17+
debouncer: Debouncer<FsEventWatcher>,
18+
}
19+
20+
impl ChangeNotifier {
21+
pub fn new(
22+
server: Arc<Mutex<ServerSocket>>,
23+
project: &Project,
24+
handle: Handle,
25+
) -> Result<Self> {
26+
let handle_clone = handle.clone();
27+
let target_path = project.root().join("target");
28+
let mut debouncer = new_debouncer(
29+
Duration::from_secs(2),
30+
move |res: DebounceEventResult| match res {
31+
Ok(events) => events.iter().for_each(|e| {
32+
handle_event(e, server.clone(), handle_clone.clone(), target_path.clone())
33+
}),
34+
Err(e) => tracing::error!("Error {:?}", e),
35+
},
36+
)?;
37+
38+
// We watch the root folder
39+
debouncer
40+
.watcher()
41+
.watch(project.root(), RecursiveMode::Recursive)?;
42+
Ok(Self { debouncer })
43+
}
44+
}
45+
46+
fn handle_event(
47+
event: &DebouncedEvent,
48+
server: Arc<Mutex<ServerSocket>>,
49+
handle: Handle,
50+
target_path: PathBuf,
51+
) {
52+
// Don't trigger lsp on target files. Otherwise it will trigger itself.
53+
if event.path.starts_with(&target_path) {
54+
return;
55+
}
56+
tracing::trace!("Event {:?} for {:?}", event.kind, event.path);
57+
let url = match Url::from_file_path(event.path.clone()) {
58+
Ok(url) => url,
59+
Err(e) => {
60+
tracing::error!("Failed to convert file path to URL: {:?}", e);
61+
return;
62+
}
63+
};
64+
handle.spawn(async move {
65+
match server
66+
.lock()
67+
.await
68+
.did_change_watched_files(DidChangeWatchedFilesParams {
69+
changes: vec![FileEvent::new(url, FileChangeType::CHANGED)],
70+
}) {
71+
Ok(_) => (),
72+
Err(e) => tracing::error!("Failed to send DidChangeWatchedFiles notification: {:?}", e),
73+
}
74+
});
75+
}

src/lsp/client_state.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ use lsp_types::{
1111
};
1212

1313
// Old and new token names.
14-
const RA_INDEXING_TOKENS: &[&str] = &["rustAnalyzer/Indexing", "rustAnalyzer/cachePriming"];
14+
const RA_INDEXING_TOKENS: &[&str] = &[
15+
"rustAnalyzer/Indexing",
16+
"rustAnalyzer/cachePriming",
17+
"rustAnalyzer/Building",
18+
];
1519

1620
pub struct ClientState {
1721
project: PathBuf,
@@ -47,13 +51,8 @@ impl LanguageClient for ClientState {
4751
tracing::error!("Failed to send indexing notification: {}", e);
4852
}
4953

50-
// Send a notification without consuming the sender
5154
if let Some(tx) = &self.indexed_tx {
52-
// Use try_send or send_async depending on whether you want it to be blocking
53-
// or potentially fail if the channel is full (though capacity is 1 here).
54-
// try_send is likely fine if the receiver is waiting.
5555
if let Err(e) = tx.try_send(()) {
56-
// Log if sending fails (e.g., channel full or disconnected)
5756
tracing::error!("Failed to send indexing completion signal: {}", e);
5857
}
5958
}

src/lsp/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod change_notifier;
12
mod client_state;
23
mod rust_analyzer_lsp;
34
mod utils;

src/lsp/rust_analyzer_lsp.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::path::Path;
22
use std::process::Stdio;
3+
use std::sync::Arc;
34

45
use anyhow::{Context, Result};
56
use async_lsp::concurrency::ConcurrencyLayer;
@@ -21,6 +22,7 @@ use tokio::task::JoinHandle;
2122
use tower::ServiceBuilder;
2223
use tracing::{debug, info};
2324

25+
use super::change_notifier::ChangeNotifier;
2426
use super::client_state::ClientState;
2527
use crate::lsp::LspNotification;
2628
use crate::project::Project;
@@ -29,10 +31,12 @@ use flume::Sender;
2931
#[derive(Debug)]
3032
pub struct RustAnalyzerLsp {
3133
project: Project,
32-
server: Mutex<ServerSocket>,
34+
server: Arc<Mutex<ServerSocket>>,
3335
#[allow(dead_code)] // Keep the handle to ensure the mainloop runs
3436
mainloop_handle: Mutex<Option<JoinHandle<()>>>,
3537
indexed_rx: Mutex<flume::Receiver<()>>,
38+
#[allow(dead_code)] // Keep the handle to ensure the change notifier runs
39+
change_notifier: ChangeNotifier,
3640
}
3741

3842
impl RustAnalyzerLsp {
@@ -69,11 +73,18 @@ impl RustAnalyzerLsp {
6973
}
7074
});
7175

76+
let server = Arc::new(Mutex::new(server));
77+
78+
// Get the current runtime handle
79+
let handle = tokio::runtime::Handle::current();
80+
let change_notifier = ChangeNotifier::new(server.clone(), project, handle)?;
81+
7282
let client = Self {
7383
project: project.clone(),
74-
server: Mutex::new(server),
84+
server,
7585
mainloop_handle: Mutex::new(Some(mainloop_handle)),
7686
indexed_rx: Mutex::new(indexed_rx),
87+
change_notifier,
7788
};
7889

7990
// Initialize.

0 commit comments

Comments
 (0)