From 339f5893a72d89e79f1e8be138839c57c626d306 Mon Sep 17 00:00:00 2001 From: he1pa <18012015693@163.com> Date: Mon, 4 Nov 2024 20:28:09 +0800 Subject: [PATCH] feat: lsp file watcher Signed-off-by: he1pa <18012015693@163.com> --- kclvm/Cargo.lock | 45 +++++++++++++++++- kclvm/tools/src/LSP/Cargo.toml | 3 +- kclvm/tools/src/LSP/src/state.rs | 79 +++++++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 5 deletions(-) diff --git a/kclvm/Cargo.lock b/kclvm/Cargo.lock index 6ac4aef66..dd7fba027 100644 --- a/kclvm/Cargo.lock +++ b/kclvm/Cargo.lock @@ -1535,6 +1535,17 @@ dependencies = [ "libc", ] +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + [[package]] name = "inotify-sys" version = "0.1.5" @@ -1734,6 +1745,7 @@ dependencies = [ "lsp-server", "lsp-types", "maplit", + "notify 7.0.0", "parking_lot 0.12.3", "proc_macro_crate", "ra_ap_vfs", @@ -2453,6 +2465,7 @@ checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi", "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -2482,7 +2495,7 @@ dependencies = [ "crossbeam-channel", "filetime", "fsevent-sys", - "inotify", + "inotify 0.9.6", "kqueue", "libc", "mio 0.8.11", @@ -2500,7 +2513,7 @@ dependencies = [ "crossbeam-channel", "filetime", "fsevent-sys", - "inotify", + "inotify 0.9.6", "kqueue", "libc", "log", @@ -2509,6 +2522,34 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.6.0", + "filetime", + "fsevent-sys", + "inotify 0.10.2", + "kqueue", + "libc", + "log", + "mio 1.0.1", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7393c226621f817964ffb3dc5704f9509e107a8b024b489cc2c1b217378785df" +dependencies = [ + "instant", +] + [[package]] name = "num-bigint" version = "0.4.6" diff --git a/kclvm/tools/src/LSP/Cargo.toml b/kclvm/tools/src/LSP/Cargo.toml index 08fdffad4..2e5ec3f8d 100644 --- a/kclvm/tools/src/LSP/Cargo.toml +++ b/kclvm/tools/src/LSP/Cargo.toml @@ -37,13 +37,14 @@ anyhow = { version = "1.0", default-features = false, features = ["std"] } crossbeam-channel = { version = "0.5.7", default-features = false } ra_ap_vfs = "0.0.149" ra_ap_vfs-notify = "0.0.149" -lsp-types = { version = "0.93.0", features = ["proposed"]} +lsp-types = { version = "0.93.0", features = ["proposed"] } threadpool = { version = "1.8.1", default-features = false } salsa = { version = "0.16.1", default-features = false } serde_json = { version = "1.0", default-features = false } parking_lot = { version = "0.12.0", default-features = false } rustc-hash = { version = "1.1.0", default-features = false } proc_macro_crate = { path = "../../benches/proc_macro_crate" } +notify = "7.0.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.37.0", features = ["full"] } diff --git a/kclvm/tools/src/LSP/src/state.rs b/kclvm/tools/src/LSP/src/state.rs index edc5f8360..0e360c72f 100644 --- a/kclvm/tools/src/LSP/src/state.rs +++ b/kclvm/tools/src/LSP/src/state.rs @@ -16,13 +16,15 @@ use lsp_types::{ notification::{Notification, PublishDiagnostics}, InitializeParams, PublishDiagnosticsParams, WorkspaceFolder, }; +use notify::{RecursiveMode, Watcher}; use parking_lot::RwLock; use ra_ap_vfs::{ChangeKind, ChangedFile, FileId, Vfs}; use std::collections::HashMap; +use std::path::PathBuf; use std::sync::Mutex; use std::thread; use std::time::{Duration, SystemTime}; -use std::{sync::Arc, time::Instant}; +use std::{sync::mpsc, sync::Arc, time::Instant}; pub(crate) type RequestHandler = fn(&mut LanguageServerState, lsp_server::Response); @@ -41,6 +43,13 @@ pub(crate) enum Task { pub(crate) enum Event { Task(Task), Lsp(lsp_server::Message), + FileWatcher(FileWatcherEvent), +} + +#[allow(unused)] +#[derive(Debug, Clone)] +pub(crate) enum FileWatcherEvent { + ChangedModFile(Vec), } pub(crate) struct Handle { @@ -96,6 +105,10 @@ pub(crate) struct LanguageServerState { /// Process files that are not in any defined workspace and delete the workspace when closing the file pub temporary_workspace: Arc>>>, pub workspace_folders: Option>, + pub fs_event_watcher: Handle< + Box, + mpsc::Receiver>, + >, } /// A snapshot of the state of the language server @@ -134,6 +147,16 @@ impl LanguageServerState { Handle { handle, _receiver } }; + let fs_event_watcher = { + let (tx, rx) = mpsc::channel::>(); + let mut watcher = notify::recommended_watcher(tx).unwrap(); + let handle = Box::new(watcher); + Handle { + handle, + _receiver: rx, + } + }; + let mut state = LanguageServerState { sender, request_queue: ReqQueue::default(), @@ -154,6 +177,7 @@ impl LanguageServerState { workspace_config_cache: KCLWorkSpaceConfigCache::default(), temporary_workspace: Arc::new(RwLock::new(HashMap::new())), workspace_folders: initialize_params.workspace_folders.clone(), + fs_event_watcher, }; state.init_workspaces(); @@ -164,9 +188,40 @@ impl LanguageServerState { /// Blocks until a new event is received from one of the many channels the language server /// listens to. Returns the first event that is received. fn next_event(&self, receiver: &Receiver) -> Option { + // if let Some(event) = self.fs_event_watcher._receiver.try_iter().next() { + for event in self.fs_event_watcher._receiver.try_iter() { + if let Ok(e) = event { + match e.kind { + notify::EventKind::Modify(modify_kind) => match modify_kind { + notify::event::ModifyKind::Data(data_change) => match data_change { + notify::event::DataChange::Content => { + let paths = e.paths; + let kcl_mod_file: Vec = paths + .iter() + .filter(|p| { + p.file_name().map(|n| n.to_str().unwrap()) + == Some(kclvm_config::modfile::KCL_MOD_FILE) + }) + .map(|p| p.clone()) + .collect(); + if !kcl_mod_file.is_empty() { + return Some(Event::FileWatcher( + FileWatcherEvent::ChangedModFile(kcl_mod_file), + )); + } + } + _ => {} + }, + _ => {} + }, + _ => {} + } + } + } + select! { recv(receiver) -> msg => msg.ok().map(Event::Lsp), - recv(self.task_receiver) -> task => Some(Event::Task(task.unwrap())) + recv(self.task_receiver) -> task => Some(Event::Task(task.unwrap())), } } @@ -197,6 +252,9 @@ impl LanguageServerState { _ => {} } } + Event::FileWatcher(file_watcher_event) => { + self.handle_file_watcher_event(file_watcher_event)? + } }; // 2. Process changes @@ -397,6 +455,18 @@ impl LanguageServerState { Ok(()) } + /// Handles a task sent by another async task + #[allow(clippy::unnecessary_wraps)] + fn handle_file_watcher_event(&mut self, event: FileWatcherEvent) -> anyhow::Result<()> { + match event { + FileWatcherEvent::ChangedModFile(path) => { + //todo + eprintln!("handle_file_watcher_event {:?}", path); + } + } + Ok(()) + } + /// Sends a response to the client. This method logs the time it took us to reply /// to a request from the client. pub(super) fn respond(&mut self, response: lsp_server::Response) -> anyhow::Result<()> { @@ -464,6 +534,11 @@ impl LanguageServerState { if let Some(workspace_folders) = &self.workspace_folders { for folder in workspace_folders { let path = file_path_from_url(&folder.uri).unwrap(); + let mut watcher = &mut self.fs_event_watcher.handle; + watcher + .watch(std::path::Path::new(&path), RecursiveMode::Recursive) + .unwrap(); + self.log_message(format!("Start watch {:?}", path)); let tool = Arc::clone(&self.tool); let (workspaces, failed) = lookup_compile_workspaces(&*tool.read(), &path, true);