Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 115 additions & 0 deletions crates/djls-server/src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! Concrete Salsa database implementation for the Django Language Server.
//!
//! This module provides the concrete [`DjangoDatabase`] that implements all
//! the database traits from workspace and template crates. This follows Ruff's
//! architecture pattern where the concrete database lives at the top level.

use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;

use dashmap::DashMap;
use djls_templates::db::Db as TemplateDb;
use djls_workspace::db::Db as WorkspaceDb;
use djls_workspace::db::SourceFile;
use djls_workspace::FileKind;
use djls_workspace::FileSystem;
use salsa::Setter;

/// Concrete Salsa database for the Django Language Server.
///
/// This database implements all the traits from various crates:
/// - [`WorkspaceDb`] for file system access and core operations
/// - [`TemplateDb`] for template parsing and diagnostics
#[salsa::db]
#[derive(Clone)]
pub struct DjangoDatabase {
/// File system for reading file content (checks buffers first, then disk).
fs: Arc<dyn FileSystem>,

/// Maps paths to [`SourceFile`] entities for O(1) lookup.
files: Arc<DashMap<PathBuf, SourceFile>>,

storage: salsa::Storage<Self>,
}

impl DjangoDatabase {
/// Create a new [`DjangoDatabase`] with the given file system and file map.
pub fn new(file_system: Arc<dyn FileSystem>, files: Arc<DashMap<PathBuf, SourceFile>>) -> Self {
Self {
storage: salsa::Storage::new(None),
fs: file_system,
files,
}
}

/// Get an existing [`SourceFile`] for the given path without creating it.
///
/// Returns `Some(SourceFile)` if the file is already tracked, `None` otherwise.
pub fn get_file(&self, path: &Path) -> Option<SourceFile> {
self.files.get(path).map(|file_ref| *file_ref)
}

/// Get or create a [`SourceFile`] for the given path.
///
/// Files are created with an initial revision of 0 and tracked in the database's
/// `DashMap`. The `Arc` ensures cheap cloning while maintaining thread safety.
pub fn get_or_create_file(&mut self, path: &PathBuf) -> SourceFile {
if let Some(file_ref) = self.files.get(path) {
return *file_ref;
}

// File doesn't exist, so we need to create it
let kind = FileKind::from_path(path);
let file = SourceFile::new(self, kind, Arc::from(path.to_string_lossy().as_ref()), 0);

self.files.insert(path.clone(), file);
file
}

/// Check if a file is being tracked without creating it.
pub fn has_file(&self, path: &Path) -> bool {
self.files.contains_key(path)
}

/// Touch a file to mark it as modified, triggering re-evaluation of dependent queries.
///
/// Updates the file's revision number to signal that cached query results
/// depending on this file should be invalidated.
pub fn touch_file(&mut self, path: &Path) {
let Some(file_ref) = self.files.get(path) else {
tracing::debug!("File {} not tracked, skipping touch", path.display());
return;
};
let file = *file_ref;
drop(file_ref); // Explicitly drop to release the lock

let current_rev = file.revision(self);
let new_rev = current_rev + 1;
file.set_revision(self).to(new_rev);

tracing::debug!(
"Touched {}: revision {} -> {}",
path.display(),
current_rev,
new_rev
);
}
}

#[salsa::db]
impl salsa::Database for DjangoDatabase {}

#[salsa::db]
impl WorkspaceDb for DjangoDatabase {
fn fs(&self) -> Arc<dyn FileSystem> {
self.fs.clone()
}

fn read_file_content(&self, path: &Path) -> std::io::Result<String> {
self.fs.read_to_string(path)
}
}

#[salsa::db]
impl TemplateDb for DjangoDatabase {}
1 change: 1 addition & 0 deletions crates/djls-server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod completions;
mod db;
mod logging;
mod queue;
pub mod server;
Expand Down
Loading