This commit is contained in:
Josh Thomas 2025-08-22 14:18:39 -05:00
parent 66cc577569
commit cabb77f67d
8 changed files with 419 additions and 2 deletions

View file

@ -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]

218
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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 }

View file

@ -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<Option<serde_json::Value>> {
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...");

View file

@ -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> = 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)
}
}
}
}

View file

@ -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<u32> {
self.line_starts.get(line).copied()
}
/// Get total content length
pub fn length(&self) -> u32 {
self.length
}
}
#[derive(Clone, Debug, PartialEq)]

View file

@ -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::<tower_lsp_server::lsp_types::Uri>() {
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<CompletionItem> = 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::<tower_lsp_server::lsp_types::Uri>() {
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<String> {
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<TemplateTagContext> {
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()
})
}
}