diff --git a/Cargo.lock b/Cargo.lock index 2446a4c..8770c8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,6 +265,7 @@ dependencies = [ "taplo", "tokio", "tower-lsp", + "tracing", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index efdbe59..065830c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ anyhow = "1.0.88" clap = { version = "4.5.18", features = ["derive"] } once_cell = "1.19.0" semver = "1.0.23" +tracing = "0.1.40" openssl = { version = '0.10', optional = true } diff --git a/src/controller.rs b/src/controller.rs index 3a350d5..0ae7b0c 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,5 +1,6 @@ pub mod appraiser; mod cargo; +mod change_timer; mod code_action; mod hover; diff --git a/src/controller/appraiser.rs b/src/controller/appraiser.rs index 329c9c7..2c247f6 100644 --- a/src/controller/appraiser.rs +++ b/src/controller/appraiser.rs @@ -15,6 +15,7 @@ use crate::{ use super::{ cargo::{parse_cargo_output, CargoResolveOutput}, + change_timer::ChangeTimer, hover::hover, }; @@ -41,6 +42,7 @@ pub enum CargoDocumentEvent { Opened(CargoTomlPayload), Saved(CargoTomlPayload), Changed(CargoTomlPayload), + ChangeTimer(Ctx), //reset state, String is path Closed(Url), //result from cargo command @@ -74,16 +76,21 @@ impl Appraiser { let tx_for_cargo = tx.clone(); tokio::spawn(async move { while let Some(event) = cargo_rx.recv().await { - let output = parse_cargo_output(&event).await; - if let Err(e) = tx_for_cargo - .send(CargoDocumentEvent::CargoResolved(output)) - .await - { - eprintln!("cargo resolved tx error: {}", e); + if let Some(output) = parse_cargo_output(&event).await { + if let Err(e) = tx_for_cargo + .send(CargoDocumentEvent::CargoResolved(output)) + .await + { + eprintln!("cargo resolved tx error: {}", e); + } } } }); + //timer task + let timer = ChangeTimer::new(tx.clone(), 3000); + let timer_tx = timer.spawn(); + //main loop //render task sender let render_tx = self.render_tx.clone(); @@ -149,7 +156,7 @@ impl Appraiser { } } CargoDocumentEvent::Changed(msg) => { - let diff = state.partial_reconsile(&msg.uri, &msg.text); + let diff = state.reconsile(&msg.uri, &msg.text); let doc = state.state(&msg.uri).unwrap(); for v in &diff.range_updated { if let Some(node) = doc.symbol(v) { @@ -182,77 +189,24 @@ impl Appraiser { .await .unwrap(); } - } - CargoDocumentEvent::Opened(msg) | CargoDocumentEvent::Saved(msg) => { - let diff = state.reconsile(&msg.uri, &msg.text); - let doc = state.state(&msg.uri).unwrap(); - - // Loop through both created and changed nodes - for v in &diff.created { - // Send to a dedicated render task - if let Some(n) = doc.symbol(v) { - render_tx - .send(DecorationEvent::DependencyLoading( - msg.uri.clone(), - v.to_string(), - n.range, - )) - .await - .unwrap(); - } - } - - for v in diff.range_updated.iter().chain(diff.value_updated.iter()) { - // Send to a dedicated render task - if let Some(n) = doc.symbol(v) { - render_tx - .send(DecorationEvent::DependencyLoading( - msg.uri.clone(), - v.to_string(), - n.range, - )) - .await - .unwrap(); - } - } - - for v in &diff.deleted { - render_tx - .send(DecorationEvent::DependencyRemove( - msg.uri.clone(), - v.to_string(), - )) - .await - .unwrap(); - } - - //no change to resolve - if !doc.is_dirty() { - continue; - } - - for v in doc.dirty_nodes.keys() { - if let Some(n) = doc.symbol(v) { - render_tx - .send(DecorationEvent::DependencyLoading( - msg.uri.clone(), - v.to_string(), - n.range, - )) - .await - .unwrap(); - } - } - - //resolve cargo dependencies in another task - cargo_tx + timer_tx .send(Ctx { - uri: msg.uri.clone(), + uri: msg.uri, rev: doc.rev, }) .await .unwrap(); } + CargoDocumentEvent::Opened(msg) | CargoDocumentEvent::Saved(msg) => { + let _ = state.reconsile(&msg.uri, &msg.text); + + start_resolve(&msg.uri, &mut state, &render_tx, &cargo_tx).await; + } + CargoDocumentEvent::ChangeTimer(ctx) => { + if state.check_rev(&ctx.uri, ctx.rev) { + start_resolve(&ctx.uri, &mut state, &render_tx, &cargo_tx).await; + } + } CargoDocumentEvent::CargoResolved(mut output) => { let Some(doc) = state.state_mut_with_rev(&output.ctx.uri, output.ctx.rev) else { @@ -359,3 +313,43 @@ impl Appraiser { tx } } + +async fn start_resolve( + uri: &Url, + state: &mut Workspace, + render_tx: &Sender, + cargo_tx: &Sender, +) { + //start from here the resolve process + state.populate_dependencies(uri); + let doc = state.state(uri).unwrap(); + + //no change to resolve + if !doc.is_dirty() { + return; + } + + for v in doc.dirty_nodes.keys() { + if let Some(n) = doc.symbol(v) { + render_tx + .send(DecorationEvent::DependencyLoading( + uri.clone(), + v.to_string(), + n.range, + )) + .await + .unwrap(); + } + } + + //resolve cargo dependencies in another task + if let Err(e) = cargo_tx + .send(Ctx { + uri: uri.clone(), + rev: doc.rev, + }) + .await + { + eprintln!("cargo resolve tx error: {}", e); + } +} diff --git a/src/controller/cargo.rs b/src/controller/cargo.rs index b474675..4847398 100644 --- a/src/controller/cargo.rs +++ b/src/controller/cargo.rs @@ -31,18 +31,20 @@ pub struct CargoResolveOutput { pub summaries: HashMap>, } -pub async fn parse_cargo_output(ctx: &Ctx) -> CargoResolveOutput { +pub async fn parse_cargo_output(ctx: &Ctx) -> Option { //TODO refactor let Ok(path) = ctx.uri.to_file_path() else { - return CargoResolveOutput { - ctx: ctx.clone(), - dependencies: HashMap::new(), - summaries: HashMap::new(), - }; + return None; + }; + let Ok(gctx) = cargo::util::context::GlobalContext::default() else { + return None; + }; + let Ok(workspace) = cargo::core::Workspace::new(path.as_path(), &gctx) else { + return None; + }; + let Ok(current) = workspace.current() else { + return None; }; - let gctx = cargo::util::context::GlobalContext::default().unwrap(); - let workspace = cargo::core::Workspace::new(path.as_path(), &gctx).unwrap(); - let current = workspace.current().unwrap(); let deps = current.dependencies(); let mut edge_kinds = HashSet::with_capacity(3); @@ -102,12 +104,12 @@ pub async fn parse_cargo_output(ctx: &Ctx) -> CargoResolveOutput { } } - CargoResolveOutput { + Some(CargoResolveOutput { ctx: ctx.clone(), dependencies: res, //TODO maybe reuse gctx summaries: summaries_map(path.as_path()), - } + }) } //TODO the current Vec didn't include yanked diff --git a/src/controller/change_timer.rs b/src/controller/change_timer.rs new file mode 100644 index 0000000..24543f0 --- /dev/null +++ b/src/controller/change_timer.rs @@ -0,0 +1,56 @@ +use std::time::Duration; + +use tokio::sync::mpsc::{self, Sender}; +use tokio::time::{sleep, Instant}; +use tower_lsp::lsp_types::Url; + +use super::{appraiser::Ctx, CargoDocumentEvent}; + +//change timer +pub struct ChangeTimer { + tx: Sender, + timeout: u64, +} + +impl ChangeTimer { + pub fn new(tx: Sender, timeout: u64) -> Self { + Self { tx, timeout } + } + + pub fn spawn(&self) -> Sender { + //create a tokio mpsc channel + let (internal_tx, mut internal_rx) = mpsc::channel::(32); + let tx = self.tx.clone(); + let timeout = self.timeout; + tokio::spawn(async move { + let mut uri: Option = None; + let mut rev = 0; + let delay = sleep(Duration::from_millis(timeout)); + tokio::pin!(delay); + loop { + tokio::select! { + Some(ctx) = internal_rx.recv() => { + uri = Some(ctx.uri.clone()); + rev = ctx.rev; + // Reset the delay to 1 second after receiving a message + delay.as_mut().reset(Instant::now() + Duration::from_millis(timeout)); + } + _ = &mut delay => { + if let Some(current_uri) = &uri { + let ctx = Ctx { + uri: current_uri.clone(), + rev, + }; + if let Err(e) = tx.send(CargoDocumentEvent::ChangeTimer(ctx)).await { + eprintln!("Failed to send Ctx: {}", e); + } + } + // Reset the delay after it has elapsed + delay.as_mut().reset(Instant::now() + Duration::from_millis(timeout)); + } + } + } + }); + internal_tx + } +} diff --git a/src/entity.rs b/src/entity.rs index 9c2dab6..1b37310 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -1,8 +1,10 @@ mod dependency; mod manifest; mod package; +mod symbol; mod table; pub use dependency::*; pub use manifest::*; +pub use symbol::*; pub use table::*; diff --git a/src/entity/symbol.rs b/src/entity/symbol.rs new file mode 100644 index 0000000..944bfc3 --- /dev/null +++ b/src/entity/symbol.rs @@ -0,0 +1,6 @@ +pub struct SymbolDiff { + pub created: Vec, + pub range_updated: Vec, + pub value_updated: Vec, + pub deleted: Vec, +} diff --git a/src/main.rs b/src/main.rs index 50f0735..0f822cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -203,7 +203,13 @@ impl LanguageServer for CargoAppraiser { .send(CargoDocumentEvent::CodeAction(uri, params.range, tx)) .await { - eprintln!("error sending code action event: {}", e); + self.client + .log_message( + MessageType::ERROR, + &format!("error sending code action event: {}", e), + ) + .await; + return Ok(None); }; match rx.await { Ok(code_action) => return Ok(Some(code_action)), @@ -230,7 +236,13 @@ impl LanguageServer for CargoAppraiser { )) .await { - eprintln!("error sending hover event: {}", e); + self.client + .log_message( + MessageType::ERROR, + &format!("error sending hover event: {}", e), + ) + .await; + return Ok(None); }; match rx.await { Ok(hover) => return Ok(Some(hover)), diff --git a/src/usecase/document.rs b/src/usecase/document.rs index 74b922d..d693da6 100644 --- a/src/usecase/document.rs +++ b/src/usecase/document.rs @@ -2,13 +2,9 @@ use std::collections::HashMap; use tower_lsp::lsp_types::{Position, Url}; -use crate::entity::{cargo_dependency_to_toml_key, CargoNode, Dependency}; +use crate::entity::{cargo_dependency_to_toml_key, CargoNode, Dependency, SymbolDiff}; -use super::{ - diff_symbols, - symbol_tree::{self, SymbolDiff}, - ReverseSymbolTree, Walker, -}; +use super::{diff_symbols, ReverseSymbolTree, Walker}; #[derive(Debug, Clone)] pub struct Document { @@ -24,15 +20,7 @@ pub struct Document { impl Document { pub fn parse(uri: &Url, text: &str) -> Self { let p = taplo::parser::parse(text); - if !p.errors.is_empty() { - //TODO - unreachable!() - } let dom = p.into_dom(); - if dom.validate().is_err() { - //TODO - unreachable!() - } let table = dom.as_table().unwrap(); let entries = table.entries().read(); @@ -101,9 +89,13 @@ impl Document { //get dependencies let gctx = cargo::util::context::GlobalContext::default().unwrap(); //TODO ERROR parse manifest - let workspace = cargo::core::Workspace::new(path.as_path(), &gctx).unwrap(); + let Ok(workspace) = cargo::core::Workspace::new(path.as_path(), &gctx) else { + return; + }; //TODO if it's error, it's a virtual workspace - let current = workspace.current().unwrap(); + let Ok(current) = workspace.current() else { + return; + }; let mut unresolved = HashMap::with_capacity(current.dependencies().len()); for dep in current.dependencies() { let key = cargo_dependency_to_toml_key(dep); diff --git a/src/usecase/symbol_tree.rs b/src/usecase/symbol_tree.rs index 76d85a0..6a89a99 100644 --- a/src/usecase/symbol_tree.rs +++ b/src/usecase/symbol_tree.rs @@ -9,7 +9,7 @@ use tower_lsp::lsp_types::{Position, Range}; use crate::entity::{ CargoKey, CargoNode, CargoTable, Dependency, DependencyKey, InvalidDependencyKey, InvalideKey, - Value, + SymbolDiff, Value, }; pub struct Walker { @@ -384,13 +384,6 @@ fn into_lsp_range(range: lsp_async_stub::util::Range) -> Range { } } -pub struct SymbolDiff { - pub created: Vec, - pub range_updated: Vec, - pub value_updated: Vec, - pub deleted: Vec, -} - //now only diff dependency nodes pub fn diff_symbols( old_map: Option<&HashMap>, diff --git a/src/usecase/workspace.rs b/src/usecase/workspace.rs index 1c64f2b..ccf6e91 100644 --- a/src/usecase/workspace.rs +++ b/src/usecase/workspace.rs @@ -1,9 +1,10 @@ use std::collections::{hash_map::Entry, HashMap}; -use cargo::ops::new; use tower_lsp::lsp_types::Url; -use super::{document::Document, symbol_tree::SymbolDiff}; +use crate::entity::SymbolDiff; + +use super::document::Document; pub struct Workspace { pub cur_uri: Option, @@ -22,6 +23,10 @@ impl Workspace { self.documents.get(uri) } + pub fn check_rev(&self, uri: &Url, rev: usize) -> bool { + self.state(uri).map(|doc| doc.rev == rev).unwrap_or(false) + } + pub fn state_mut(&mut self, uri: &Url) -> Option<&mut Document> { self.documents.get_mut(uri) } @@ -46,25 +51,6 @@ impl Workspace { } } - pub fn partial_reconsile(&mut self, uri: &Url, text: &str) -> SymbolDiff { - let mut new_doc = Document::parse(uri, text); - self.cur_uri = Some(uri.clone()); - //TODO refactor, maybe we can do better to avoid string allocation - match self.documents.entry(uri.clone()) { - Entry::Occupied(mut entry) => { - let diff = Document::diff_symbols(Some(entry.get()), &new_doc); - entry.get_mut().reconsile(new_doc, &diff); - diff - } - Entry::Vacant(entry) => { - let diff = Document::diff_symbols(None, &new_doc); - new_doc.self_reconsile(&diff); - entry.insert(new_doc); - diff - } - } - } - pub fn reconsile(&mut self, uri: &Url, text: &str) -> SymbolDiff { let mut new_doc = Document::parse(uri, text); self.cur_uri = Some(uri.clone()); @@ -84,4 +70,10 @@ impl Workspace { } } } + + pub fn populate_dependencies(&mut self, uri: &Url) { + if let Some(doc) = self.state_mut(uri) { + doc.populate_dependencies(); + } + } }