mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-14 14:25:36 +00:00
Remove vestigal concrete Project database, keeping trait (#198)
Some checks failed
lint / pre-commit (push) Has been cancelled
lint / rustfmt (push) Has been cancelled
lint / clippy (push) Has been cancelled
lint / cargo-check (push) Has been cancelled
release / build (push) Has been cancelled
release / test (push) Has been cancelled
test / generate-matrix (push) Has been cancelled
zizmor 🌈 / zizmor latest via PyPI (push) Has been cancelled
release / release (push) Has been cancelled
test / Python , Django () (push) Has been cancelled
test / tests (push) Has been cancelled
Some checks failed
lint / pre-commit (push) Has been cancelled
lint / rustfmt (push) Has been cancelled
lint / clippy (push) Has been cancelled
lint / cargo-check (push) Has been cancelled
release / build (push) Has been cancelled
release / test (push) Has been cancelled
test / generate-matrix (push) Has been cancelled
zizmor 🌈 / zizmor latest via PyPI (push) Has been cancelled
release / release (push) Has been cancelled
test / Python , Django () (push) Has been cancelled
test / tests (push) Has been cancelled
This commit is contained in:
parent
318a395d6f
commit
5974c51383
10 changed files with 142 additions and 68 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -459,6 +459,7 @@ name = "djls-project"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"djls-dev",
|
||||
"djls-workspace",
|
||||
"pyo3",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
|
@ -513,7 +514,6 @@ dependencies = [
|
|||
"anyhow",
|
||||
"camino",
|
||||
"dashmap",
|
||||
"djls-project",
|
||||
"notify",
|
||||
"percent-encoding",
|
||||
"salsa",
|
||||
|
|
|
@ -8,6 +8,8 @@ extension-module = []
|
|||
default = []
|
||||
|
||||
[dependencies]
|
||||
djls-workspace = { workspace = true }
|
||||
|
||||
pyo3 = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
which = { workspace = true}
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
use crate::meta::ProjectMetadata;
|
||||
//! Project-specific database trait and queries.
|
||||
//!
|
||||
//! This module extends the workspace database trait with project-specific
|
||||
//! functionality including metadata access and Python environment discovery.
|
||||
|
||||
use djls_workspace::Db as WorkspaceDb;
|
||||
|
||||
use crate::meta::ProjectMetadata;
|
||||
use crate::python::PythonEnvironment;
|
||||
|
||||
/// Project-specific database trait extending the workspace database
|
||||
#[salsa::db]
|
||||
pub trait Db: salsa::Database {
|
||||
pub trait Db: WorkspaceDb {
|
||||
/// Get the project metadata containing root path and venv configuration
|
||||
fn metadata(&self) -> &ProjectMetadata;
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
pub struct ProjectDatabase {
|
||||
storage: salsa::Storage<ProjectDatabase>,
|
||||
metadata: ProjectMetadata,
|
||||
/// Find the Python environment for the project.
|
||||
///
|
||||
/// This Salsa tracked function discovers the Python environment based on:
|
||||
/// 1. Explicit venv path from metadata
|
||||
/// 2. VIRTUAL_ENV environment variable
|
||||
/// 3. Common venv directories in project root (.venv, venv, env, .env)
|
||||
/// 4. System Python as fallback
|
||||
#[salsa::tracked]
|
||||
pub fn find_python_environment(db: &dyn Db) -> Option<PythonEnvironment> {
|
||||
let project_path = db.metadata().root().as_path();
|
||||
let venv_path = db.metadata().venv().and_then(|p| p.to_str());
|
||||
|
||||
PythonEnvironment::new(project_path, venv_path)
|
||||
}
|
||||
|
||||
impl ProjectDatabase {
|
||||
pub fn new(metadata: ProjectMetadata) -> Self {
|
||||
let storage = salsa::Storage::new(None);
|
||||
|
||||
Self { storage, metadata }
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl Db for ProjectDatabase {
|
||||
fn metadata(&self) -> &ProjectMetadata {
|
||||
&self.metadata
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for ProjectDatabase {}
|
||||
|
|
|
@ -8,11 +8,11 @@ use std::fmt;
|
|||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use db::ProjectDatabase;
|
||||
use meta::ProjectMetadata;
|
||||
pub use db::find_python_environment;
|
||||
pub use db::Db;
|
||||
pub use meta::ProjectMetadata;
|
||||
use pyo3::prelude::*;
|
||||
use python::find_python_environment;
|
||||
use python::PythonEnvironment;
|
||||
pub use python::PythonEnvironment;
|
||||
pub use templatetags::TemplateTags;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -32,11 +32,9 @@ impl DjangoProject {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self, venv_path: Option<&str>) -> PyResult<()> {
|
||||
let venv_pathbuf = venv_path.map(PathBuf::from);
|
||||
let metadata = ProjectMetadata::new(self.path.clone(), venv_pathbuf);
|
||||
let db = ProjectDatabase::new(metadata);
|
||||
self.env = find_python_environment(&db);
|
||||
pub fn initialize(&mut self, db: &dyn Db) -> PyResult<()> {
|
||||
// Use the database to find the Python environment
|
||||
self.env = find_python_environment(db);
|
||||
if self.env.is_none() {
|
||||
return Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
|
||||
"Could not find Python environment",
|
||||
|
|
|
@ -7,14 +7,17 @@ pub struct ProjectMetadata {
|
|||
}
|
||||
|
||||
impl ProjectMetadata {
|
||||
#[must_use]
|
||||
pub fn new(root: PathBuf, venv: Option<PathBuf>) -> Self {
|
||||
ProjectMetadata { root, venv }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn root(&self) -> &PathBuf {
|
||||
&self.root
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn venv(&self) -> Option<&PathBuf> {
|
||||
self.venv.as_ref()
|
||||
}
|
||||
|
|
|
@ -4,17 +4,8 @@ use std::path::PathBuf;
|
|||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::system;
|
||||
|
||||
#[salsa::tracked]
|
||||
pub fn find_python_environment(db: &dyn Db) -> Option<PythonEnvironment> {
|
||||
let project_path = db.metadata().root().as_path();
|
||||
let venv_path = db.metadata().venv().and_then(|p| p.to_str());
|
||||
|
||||
PythonEnvironment::new(project_path, venv_path)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PythonEnvironment {
|
||||
python_path: PathBuf,
|
||||
|
@ -23,7 +14,8 @@ pub struct PythonEnvironment {
|
|||
}
|
||||
|
||||
impl PythonEnvironment {
|
||||
fn new(project_path: &Path, venv_path: Option<&str>) -> Option<Self> {
|
||||
#[must_use]
|
||||
pub fn new(project_path: &Path, venv_path: Option<&str>) -> Option<Self> {
|
||||
if let Some(path) = venv_path {
|
||||
let prefix = PathBuf::from(path);
|
||||
if let Some(env) = Self::from_venv_prefix(&prefix) {
|
||||
|
@ -703,12 +695,57 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
// Add tests for the salsa tracked function
|
||||
mod salsa_integration {
|
||||
use std::sync::Arc;
|
||||
|
||||
use djls_workspace::FileSystem;
|
||||
use djls_workspace::InMemoryFileSystem;
|
||||
|
||||
use super::*;
|
||||
use crate::db::ProjectDatabase;
|
||||
use crate::db::find_python_environment;
|
||||
use crate::db::Db as ProjectDb;
|
||||
use crate::meta::ProjectMetadata;
|
||||
|
||||
/// Test implementation of ProjectDb for unit tests
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
struct TestDatabase {
|
||||
storage: salsa::Storage<TestDatabase>,
|
||||
metadata: ProjectMetadata,
|
||||
fs: Arc<dyn FileSystem>,
|
||||
}
|
||||
|
||||
impl TestDatabase {
|
||||
fn new(metadata: ProjectMetadata) -> Self {
|
||||
Self {
|
||||
storage: salsa::Storage::new(None),
|
||||
metadata,
|
||||
fs: Arc::new(InMemoryFileSystem::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for TestDatabase {}
|
||||
|
||||
#[salsa::db]
|
||||
impl djls_workspace::Db for TestDatabase {
|
||||
fn fs(&self) -> Arc<dyn FileSystem> {
|
||||
self.fs.clone()
|
||||
}
|
||||
|
||||
fn read_file_content(&self, path: &std::path::Path) -> std::io::Result<String> {
|
||||
self.fs.read_to_string(path)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ProjectDb for TestDatabase {
|
||||
fn metadata(&self) -> &ProjectMetadata {
|
||||
&self.metadata
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_python_environment_with_salsa_db() {
|
||||
let project_dir = tempdir().unwrap();
|
||||
|
@ -721,8 +758,8 @@ mod tests {
|
|||
let metadata =
|
||||
ProjectMetadata::new(project_dir.path().to_path_buf(), Some(venv_prefix.clone()));
|
||||
|
||||
// Create a ProjectDatabase with the metadata
|
||||
let db = ProjectDatabase::new(metadata);
|
||||
// Create a TestDatabase with the metadata
|
||||
let db = TestDatabase::new(metadata);
|
||||
|
||||
// Call the tracked function
|
||||
let env = find_python_environment(&db);
|
||||
|
@ -756,8 +793,8 @@ mod tests {
|
|||
// Create a metadata instance with project path but no explicit venv path
|
||||
let metadata = ProjectMetadata::new(project_dir.path().to_path_buf(), None);
|
||||
|
||||
// Create a ProjectDatabase with the metadata
|
||||
let db = ProjectDatabase::new(metadata);
|
||||
// Create a TestDatabase with the metadata
|
||||
let db = TestDatabase::new(metadata);
|
||||
|
||||
// Mock to ensure VIRTUAL_ENV is not set
|
||||
let _guard = system::mock::MockGuard;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! 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.
|
||||
//! the database traits from workspace, template, and project crates. This follows
|
||||
//! Ruff's architecture pattern where the concrete database lives at the top level.
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
@ -11,6 +11,8 @@ use std::sync::Arc;
|
|||
use std::sync::Mutex;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use djls_project::Db as ProjectDb;
|
||||
use djls_project::ProjectMetadata;
|
||||
use djls_templates::db::Db as TemplateDb;
|
||||
use djls_workspace::db::Db as WorkspaceDb;
|
||||
use djls_workspace::db::SourceFile;
|
||||
|
@ -23,6 +25,7 @@ use salsa::Setter;
|
|||
/// This database implements all the traits from various crates:
|
||||
/// - [`WorkspaceDb`] for file system access and core operations
|
||||
/// - [`TemplateDb`] for template parsing and diagnostics
|
||||
/// - [`ProjectDb`] for project metadata and Python environment
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
pub struct DjangoDatabase {
|
||||
|
@ -32,6 +35,9 @@ pub struct DjangoDatabase {
|
|||
/// Maps paths to [`SourceFile`] entities for O(1) lookup.
|
||||
files: Arc<DashMap<PathBuf, SourceFile>>,
|
||||
|
||||
/// Project metadata containing root path and venv configuration.
|
||||
metadata: ProjectMetadata,
|
||||
|
||||
storage: salsa::Storage<Self>,
|
||||
|
||||
// The logs are only used for testing and demonstrating reuse:
|
||||
|
@ -49,6 +55,7 @@ impl Default for DjangoDatabase {
|
|||
Self {
|
||||
fs: Arc::new(InMemoryFileSystem::new()),
|
||||
files: Arc::new(DashMap::new()),
|
||||
metadata: ProjectMetadata::new(PathBuf::from("/test"), None),
|
||||
storage: salsa::Storage::new(Some(Box::new({
|
||||
let logs = logs.clone();
|
||||
move |event| {
|
||||
|
@ -68,11 +75,16 @@ impl Default for DjangoDatabase {
|
|||
}
|
||||
|
||||
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 {
|
||||
/// Create a new [`DjangoDatabase`] with the given file system, file map, and project metadata.
|
||||
pub fn new(
|
||||
file_system: Arc<dyn FileSystem>,
|
||||
files: Arc<DashMap<PathBuf, SourceFile>>,
|
||||
metadata: ProjectMetadata,
|
||||
) -> Self {
|
||||
Self {
|
||||
fs: file_system,
|
||||
files,
|
||||
metadata,
|
||||
storage: salsa::Storage::new(None),
|
||||
#[cfg(test)]
|
||||
logs: Arc::new(Mutex::new(None)),
|
||||
|
@ -149,3 +161,10 @@ impl WorkspaceDb for DjangoDatabase {
|
|||
|
||||
#[salsa::db]
|
||||
impl TemplateDb for DjangoDatabase {}
|
||||
|
||||
#[salsa::db]
|
||||
impl ProjectDb for DjangoDatabase {
|
||||
fn metadata(&self) -> &ProjectMetadata {
|
||||
&self.metadata
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,12 +149,7 @@ impl LanguageServer for DjangoLanguageServer {
|
|||
|
||||
let init_result = {
|
||||
let mut session_lock = session_arc.lock().await;
|
||||
if let Some(project) = session_lock.project_mut().as_mut() {
|
||||
project.initialize(venv_path.as_deref())
|
||||
} else {
|
||||
// Project was removed between read and write locks
|
||||
Ok(())
|
||||
}
|
||||
session_lock.initialize_project()
|
||||
};
|
||||
|
||||
match init_result {
|
||||
|
|
|
@ -9,11 +9,13 @@ use std::sync::Arc;
|
|||
use dashmap::DashMap;
|
||||
use djls_conf::Settings;
|
||||
use djls_project::DjangoProject;
|
||||
use djls_project::ProjectMetadata;
|
||||
use djls_workspace::db::SourceFile;
|
||||
use djls_workspace::paths;
|
||||
use djls_workspace::PositionEncoding;
|
||||
use djls_workspace::TextDocument;
|
||||
use djls_workspace::Workspace;
|
||||
use pyo3::PyResult;
|
||||
use tower_lsp_server::lsp_types;
|
||||
use url::Url;
|
||||
|
||||
|
@ -63,23 +65,29 @@ impl Session {
|
|||
std::env::current_dir().ok()
|
||||
});
|
||||
|
||||
let (project, settings) = if let Some(path) = &project_path {
|
||||
let (project, settings, metadata) = if let Some(path) = &project_path {
|
||||
let settings =
|
||||
djls_conf::Settings::new(path).unwrap_or_else(|_| djls_conf::Settings::default());
|
||||
|
||||
let project = Some(djls_project::DjangoProject::new(path.clone()));
|
||||
|
||||
(project, settings)
|
||||
// Create metadata for the project with venv path from settings
|
||||
let venv_path = settings.venv_path().map(PathBuf::from);
|
||||
let metadata = ProjectMetadata::new(path.clone(), venv_path);
|
||||
|
||||
(project, settings, metadata)
|
||||
} else {
|
||||
(None, Settings::default())
|
||||
// Default metadata for when there's no project path
|
||||
let metadata = ProjectMetadata::new(PathBuf::from("."), None);
|
||||
(None, Settings::default(), metadata)
|
||||
};
|
||||
|
||||
// Create workspace for buffer management
|
||||
let workspace = Workspace::new();
|
||||
|
||||
// Create the concrete database with the workspace's file system
|
||||
// Create the concrete database with the workspace's file system and metadata
|
||||
let files = Arc::new(DashMap::new());
|
||||
let db = DjangoDatabase::new(workspace.file_system(), files);
|
||||
let db = DjangoDatabase::new(workspace.file_system(), files, metadata);
|
||||
|
||||
Self {
|
||||
db,
|
||||
|
@ -130,6 +138,20 @@ impl Session {
|
|||
f(&mut self.db)
|
||||
}
|
||||
|
||||
/// Get a reference to the database for project operations.
|
||||
pub fn database(&self) -> &DjangoDatabase {
|
||||
&self.db
|
||||
}
|
||||
|
||||
/// Initialize the project with the database.
|
||||
pub fn initialize_project(&mut self) -> PyResult<()> {
|
||||
if let Some(project) = self.project.as_mut() {
|
||||
project.initialize(&self.db)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a document in the session.
|
||||
///
|
||||
/// Updates both the workspace buffers and database. Creates the file in
|
||||
|
|
|
@ -4,8 +4,6 @@ version = "0.0.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
djls-project = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
camino = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue