mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-10 20:36:21 +00:00
wip
This commit is contained in:
parent
66cc577569
commit
cabb77f67d
8 changed files with 419 additions and 2 deletions
|
@ -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 }
|
||||
|
|
|
@ -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...");
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue