From cabb77f67da02248cfaf829bc3ecd043fad38e45 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 22 Aug 2025 14:18:39 -0500 Subject: [PATCH] wip --- .just/devtools.just | 3 + Cargo.lock | 218 +++++++++++++++++++ Cargo.toml | 3 +- crates/djls-server/Cargo.toml | 1 + crates/djls-server/src/server.rs | 36 +++ crates/djls-server/src/session.rs | 31 +++ crates/djls-server/src/workspace/document.rs | 10 + crates/djls-server/src/workspace/store.rs | 119 +++++++++- 8 files changed, 419 insertions(+), 2 deletions(-) diff --git a/.just/devtools.just b/.just/devtools.just index 21b9aec..cb7aa5f 100644 --- a/.just/devtools.just +++ b/.just/devtools.just @@ -2,6 +2,8 @@ set unstable := true justfile := justfile_directory() + "/.just/devtools.just" +export DJLS_DEBUG := "1" + [private] default: @just --list --justfile {{ justfile }} @@ -22,6 +24,7 @@ fmt: [no-cd] debug: + echo $DJLS_DEBUG cargo run --bin djls-tmux --package djls-dev [no-cd] diff --git a/Cargo.lock b/Cargo.lock index 35c50ac..f9abb15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,21 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.20" @@ -162,18 +177,48 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cc" +version = "1.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clap" version = "4.5.45" @@ -281,6 +326,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -453,6 +504,7 @@ name = "djls-server" version = "0.0.0" dependencies = [ "anyhow", + "chrono", "djls-conf", "djls-dev", "djls-project", @@ -732,6 +784,30 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.10.0" @@ -792,6 +868,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "json5" version = "0.4.1" @@ -921,6 +1007,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -1295,6 +1390,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -1443,6 +1544,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.6" @@ -1916,6 +2023,64 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "which" version = "8.0.0" @@ -1949,12 +2114,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index c6dc33c..482f236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,8 @@ salsa = "0.23.0" tower-lsp-server = { version = "0.22.0", features = ["proposed"] } anyhow = "1.0" -clap = { version = "4.5", features = ["derive"] } +chrono = { version = "0.4", features = ["serde"] } +clap = { version = "4.0", features = ["derive"] } config = { version ="0.15", features = ["toml"] } directories = "6.0" percent-encoding = "2.3" diff --git a/crates/djls-server/Cargo.toml b/crates/djls-server/Cargo.toml index 372554f..bc06cf4 100644 --- a/crates/djls-server/Cargo.toml +++ b/crates/djls-server/Cargo.toml @@ -13,6 +13,7 @@ djls-project = { workspace = true } djls-templates = { workspace = true } anyhow = { workspace = true } +chrono = { workspace = true } percent-encoding = { workspace = true } pyo3 = { workspace = true } salsa = { workspace = true } diff --git a/crates/djls-server/src/server.rs b/crates/djls-server/src/server.rs index 833646f..aa401ef 100644 --- a/crates/djls-server/src/server.rs +++ b/crates/djls-server/src/server.rs @@ -2,6 +2,7 @@ use std::future::Future; use std::sync::Arc; use tokio::sync::RwLock; +use serde_json; use tower_lsp_server::jsonrpc::Result as LspResult; use tower_lsp_server::lsp_types::CompletionOptions; use tower_lsp_server::lsp_types::CompletionParams; @@ -11,6 +12,8 @@ use tower_lsp_server::lsp_types::DidChangeTextDocumentParams; use tower_lsp_server::lsp_types::DidCloseTextDocumentParams; use tower_lsp_server::lsp_types::DidOpenTextDocumentParams; use tower_lsp_server::lsp_types::DidSaveTextDocumentParams; +use tower_lsp_server::lsp_types::ExecuteCommandOptions; +use tower_lsp_server::lsp_types::ExecuteCommandParams; use tower_lsp_server::lsp_types::InitializeParams; use tower_lsp_server::lsp_types::InitializeResult; use tower_lsp_server::lsp_types::InitializedParams; @@ -129,6 +132,10 @@ impl LanguageServer for DjangoLanguageServer { save: Some(SaveOptions::default().into()), }, )), + execute_command_provider: Some(ExecuteCommandOptions { + commands: vec!["djls/dumpState".to_string()], + ..Default::default() + }), ..Default::default() }, server_info: Some(ServerInfo { @@ -274,6 +281,35 @@ impl LanguageServer for DjangoLanguageServer { .await) } + async fn execute_command(&self, params: ExecuteCommandParams) -> LspResult> { + match params.command.as_str() { + "djls/dumpState" => { + tracing::info!("Executing djls/dumpState command"); + + // Check if debug mode is enabled + if std::env::var("DJLS_DEBUG").is_err() { + tracing::warn!("djls/dumpState command requires DJLS_DEBUG environment variable"); + return Ok(Some(serde_json::json!({ + "error": "Debug mode not enabled. Set DJLS_DEBUG environment variable." + }))); + } + + let result = self.with_session(|session| { + session.dump_debug_state() + }).await; + + Ok(Some(serde_json::json!({ + "success": true, + "message": result + }))) + } + _ => { + tracing::warn!("Unknown command: {}", params.command); + Ok(None) + } + } + } + async fn did_change_configuration(&self, _params: DidChangeConfigurationParams) { tracing::info!("Configuration change detected. Reloading settings..."); diff --git a/crates/djls-server/src/session.rs b/crates/djls-server/src/session.rs index 3d8fcde..83027f0 100644 --- a/crates/djls-server/src/session.rs +++ b/crates/djls-server/src/session.rs @@ -3,6 +3,7 @@ use djls_project::DjangoProject; use salsa::StorageHandle; use tower_lsp_server::lsp_types::ClientCapabilities; use tower_lsp_server::lsp_types::InitializeParams; +use serde_json; use crate::db::ServerDatabase; use crate::workspace::Store; @@ -103,4 +104,34 @@ impl Session { let storage = self.db_handle.clone().into_storage(); ServerDatabase::new(storage) } + + /// Dump debug state to file for development/debugging purposes + pub fn dump_debug_state(&self) -> String { + use std::fs; + use chrono::{DateTime, Utc}; + + let timestamp: DateTime = Utc::now(); + let filename = format!("djls-debug-state-{}.json", timestamp.format("%Y%m%d-%H%M%S")); + + let debug_info = serde_json::json!({ + "timestamp": timestamp.to_rfc3339(), + "vfs": self.documents.debug_vfs_state(), + "store": self.documents.debug_store_state(), + "project": { + "path": self.project.as_ref().map(|p| p.path().display().to_string()), + "has_project": self.project.is_some() + } + }); + + match fs::write(&filename, serde_json::to_string_pretty(&debug_info).unwrap_or_else(|_| "{}".to_string())) { + Ok(_) => { + tracing::info!("Debug state dumped to: {}", filename); + format!("Debug state written to: {}", filename) + } + Err(e) => { + tracing::error!("Failed to write debug state: {}", e); + format!("Failed to write debug state: {}", e) + } + } + } } diff --git a/crates/djls-server/src/workspace/document.rs b/crates/djls-server/src/workspace/document.rs index 4c23f13..8892159 100644 --- a/crates/djls-server/src/workspace/document.rs +++ b/crates/djls-server/src/workspace/document.rs @@ -176,6 +176,16 @@ impl LineIndex { Position::new(u32::try_from(line).unwrap_or(0), character) } + + /// Get line start offset for a given line number + pub fn line_start(&self, line: usize) -> Option { + self.line_starts.get(line).copied() + } + + /// Get total content length + pub fn length(&self) -> u32 { + self.length + } } #[derive(Clone, Debug, PartialEq)] diff --git a/crates/djls-server/src/workspace/store.rs b/crates/djls-server/src/workspace/store.rs index 271fa20..bec6515 100644 --- a/crates/djls-server/src/workspace/store.rs +++ b/crates/djls-server/src/workspace/store.rs @@ -1,6 +1,7 @@ use super::fs::FileSystem; use std::collections::HashMap; use std::path::PathBuf; +use serde_json; use anyhow::anyhow; use anyhow::Result; @@ -23,6 +24,7 @@ use tower_lsp_server::lsp_types::Position; use super::document::ClosingBrace; use super::document::LanguageId; use super::document::LineIndex; +use super::document::TemplateTagContext; use super::document::TextDocument; use super::utils::uri_to_pathbuf; @@ -295,7 +297,35 @@ impl Store { return None; } - let context = document.get_template_tag_context(db, position)?; + // Read content from VFS instead of using salsa-tracked document + let content = if let Ok(parsed_uri) = uri.parse::() { + if let Some(absolute_path) = uri_to_pathbuf(&parsed_uri) { + if let Ok(relative_path) = absolute_path.strip_prefix(&self.root_path) { + let relative_path_str = relative_path.to_string_lossy(); + + // Try to read from VFS first (includes unsaved changes) + match self.vfs.read_to_string(&relative_path_str) { + Ok(vfs_content) => vfs_content, + Err(_) => { + // Fallback to document content if VFS read fails + document.contents(db).to_string() + } + } + } else { + // Path not within workspace, use document content + document.contents(db).to_string() + } + } else { + // URI parsing failed, use document content + document.contents(db).to_string() + } + } else { + // URI parsing failed, use document content + document.contents(db).to_string() + }; + + // Use standalone analyzer instead of salsa-tracked method + let context = Self::analyze_template_context(&content, position)?; let mut completions: Vec = tags .iter() @@ -332,4 +362,91 @@ impl Store { Some(CompletionResponse::Array(completions)) } } + + /// Debug method to expose VFS state (only enabled with DJLS_DEBUG) + pub fn debug_vfs_state(&self) -> serde_json::Value { + use std::collections::HashMap; + + // Get memory layer contents by trying to read all known documents + let mut memory_layer = HashMap::new(); + + for uri_str in self.documents.keys() { + if let Ok(uri) = uri_str.parse::() { + if let Some(absolute_path) = super::utils::uri_to_pathbuf(&uri) { + if let Ok(relative_path) = absolute_path.strip_prefix(&self.root_path) { + let relative_path_str = relative_path.to_string_lossy(); + + // Try to read from VFS - this will show us if there's content in memory layer + if let Ok(content) = self.vfs.read_to_string(&relative_path_str) { + memory_layer.insert(relative_path_str.to_string(), content); + } + } + } + } + } + + serde_json::json!({ + "memory_layer_files": memory_layer, + "physical_root": self.root_path.display().to_string() + }) + } + + /// Extract a specific line from content string + fn get_line_from_content(content: &str, line_num: u32) -> Option { + let index = LineIndex::new(content); + let start = index.line_start(line_num as usize)?; + let end = index + .line_start(line_num as usize + 1) + .unwrap_or(index.length()); + + Some(content[start as usize..end as usize].to_string()) + } + + /// Analyze template tag context from raw content (standalone, no salsa dependency) + fn analyze_template_context(content: &str, position: Position) -> Option { + let line = Self::get_line_from_content(content, position.line)?; + let char_pos: usize = position.character.try_into().ok()?; + let prefix = &line[..char_pos]; + let rest_of_line = &line[char_pos..]; + let rest_trimmed = rest_of_line.trim_start(); + + prefix.rfind("{%").map(|tag_start| { + // Check if we're immediately after {% with no space + let needs_leading_space = prefix.ends_with("{%"); + + let closing_brace = if rest_trimmed.starts_with("%}") { + ClosingBrace::FullClose + } else if rest_trimmed.starts_with('}') { + ClosingBrace::PartialClose + } else { + ClosingBrace::None + }; + + TemplateTagContext { + partial_tag: prefix[tag_start + 2..].trim().to_string(), + closing_brace, + needs_leading_space, + } + }) + } + + /// Debug method to expose Store state (only enabled with DJLS_DEBUG) + pub fn debug_store_state(&self) -> serde_json::Value { + use std::collections::HashMap; + + let mut documents_info = HashMap::new(); + + for (uri, _doc) in &self.documents { + documents_info.insert(uri.clone(), serde_json::json!({ + "version": self.versions.get(uri), + "tracked": true + })); + } + + serde_json::json!({ + "documents": documents_info, + "document_count": self.documents.len(), + "workspace_root": self.root_path.display().to_string() + }) + } }