diff --git a/crates/djls-project/src/db.rs b/crates/djls-project/src/db.rs index 64eb3f84..61334010 100644 --- a/crates/djls-project/src/db.rs +++ b/crates/djls-project/src/db.rs @@ -12,7 +12,7 @@ use std::sync::Arc; -use camino::Utf8Path; +use camino::Utf8PathBuf; use crate::inspector::pool::InspectorPool; use crate::project::Project; @@ -26,8 +26,14 @@ pub trait Db: salsa::Database { /// Get the shared inspector pool for executing Python queries fn inspector_pool(&self) -> Arc; - /// Get the project root path if a project is set - fn project_path(&self) -> Option<&Utf8Path> { - self.project().map(|p| p.root(self).as_path()) + /// Return the current project root or fall back to the current working directory. + fn project_root_or_cwd(&self) -> Utf8PathBuf { + if let Some(project) = self.project() { + project.root(self).clone() + } else if let Ok(current_dir) = std::env::current_dir() { + Utf8PathBuf::from_path_buf(current_dir).unwrap_or_else(|_| Utf8PathBuf::from(".")) + } else { + Utf8PathBuf::from(".") + } } } diff --git a/crates/djls-project/src/project.rs b/crates/djls-project/src/project.rs index 58e8a5ba..ca4c09be 100644 --- a/crates/djls-project/src/project.rs +++ b/crates/djls-project/src/project.rs @@ -1,3 +1,4 @@ +use camino::Utf8Path; use camino::Utf8PathBuf; use crate::db::Db as ProjectDb; @@ -26,6 +27,24 @@ pub struct Project { } impl Project { + pub fn bootstrap( + db: &dyn ProjectDb, + root: &Utf8Path, + venv_path: Option<&str>, + settings_module: Option<&str>, + ) -> Project { + let interpreter = venv_path + .map(|path| Interpreter::VenvPath(path.to_string())) + .or_else(|| std::env::var("VIRTUAL_ENV").ok().map(Interpreter::VenvPath)) + .unwrap_or(Interpreter::Auto); + + let django_settings = settings_module + .map(std::string::ToString::to_string) + .or_else(|| std::env::var("DJANGO_SETTINGS_MODULE").ok()); + + Project::new(db, root.to_path_buf(), interpreter, django_settings) + } + pub fn initialize(self, db: &dyn ProjectDb) { let _ = django_available(db, self); let _ = django_settings_module(db, self); diff --git a/crates/djls-server/src/db.rs b/crates/djls-server/src/db.rs index 47112444..dd14bf97 100644 --- a/crates/djls-server/src/db.rs +++ b/crates/djls-server/src/db.rs @@ -8,10 +8,9 @@ use std::sync::Arc; use std::sync::Mutex; use camino::Utf8Path; -use camino::Utf8PathBuf; +use djls_conf::Settings; use djls_project::Db as ProjectDb; use djls_project::InspectorPool; -use djls_project::Interpreter; use djls_project::Project; use djls_semantic::Db as SemanticDb; use djls_semantic::TagSpecs; @@ -75,19 +74,6 @@ impl Default for DjangoDatabase { } impl DjangoDatabase { - /// Set the project for this database instance - /// - /// # Panics - /// - /// Panics if the project mutex is poisoned. - pub fn set_project(&self, root: &Utf8Path) { - let interpreter = Interpreter::Auto; - let django_settings = std::env::var("DJANGO_SETTINGS_MODULE").ok(); - - let project = Project::new(self, root.to_path_buf(), interpreter, django_settings); - - *self.project.lock().unwrap() = Some(project); - } /// Create a new [`DjangoDatabase`] with the given file system handle. pub fn new(file_system: Arc) -> Self { Self { @@ -99,6 +85,18 @@ impl DjangoDatabase { logs: Arc::new(Mutex::new(None)), } } + + /// Set the project for this database instance + /// + /// # Panics + /// + /// Panics if the project mutex is poisoned. + pub fn set_project(&mut self, root: Option<&Utf8Path>, settings: &Settings) { + if let Some(path) = root { + let project = Project::bootstrap(self, path, settings.venv_path(), None); + *self.project.lock().unwrap() = Some(project); + } + } } #[salsa::db] @@ -124,19 +122,11 @@ impl TemplateDb for DjangoDatabase {} #[salsa::db] impl SemanticDb for DjangoDatabase { fn tag_specs(&self) -> Arc { - let project_root = if let Some(project) = self.project() { - project.root(self).clone() - } else { - std::env::current_dir() - .ok() - .and_then(|p| Utf8PathBuf::from_path_buf(p).ok()) - .unwrap_or_else(|| Utf8PathBuf::from(".")) - }; + let project_root = self.project_root_or_cwd(); - let tag_specs = if let Ok(settings) = djls_conf::Settings::new(&project_root) { - TagSpecs::from(&settings) - } else { - djls_semantic::django_builtin_specs() + let tag_specs = match djls_conf::Settings::new(&project_root) { + Ok(settings) => TagSpecs::from(&settings), + Err(_) => djls_semantic::django_builtin_specs(), }; Arc::new(tag_specs) diff --git a/crates/djls-server/src/server.rs b/crates/djls-server/src/server.rs index 8c142c5f..2e1da39d 100644 --- a/crates/djls-server/src/server.rs +++ b/crates/djls-server/src/server.rs @@ -6,6 +6,7 @@ use djls_project::Db as ProjectDb; use djls_semantic::Db as SemanticDb; use djls_source::FileKind; use djls_workspace::paths; +use djls_workspace::LanguageId; use tokio::sync::Mutex; use tower_lsp_server::jsonrpc::Result as LspResult; use tower_lsp_server::lsp_types; @@ -179,19 +180,12 @@ impl LanguageServer for DjangoLanguageServer { tracing::info!("Server received initialized notification."); self.with_session_task(move |session_arc| async move { - let session_lock = session_arc.lock().await; + let session = session_arc.lock().await; - let project_path = session_lock - .project() - .map(|p| p.root(session_lock.database()).clone()); - - if let Some(path) = project_path { + if let Some(project) = session.project() { + let path = project.root(session.database()).clone(); tracing::info!("Task: Starting initialization for project at: {}", path); - - if let Some(project) = session_lock.project() { - project.initialize(session_lock.database()); - } - + project.initialize(session.database()); tracing::info!("Task: Successfully initialized project: {}", path); } else { tracing::info!("Task: No project configured, skipping initialization."); @@ -211,21 +205,16 @@ impl LanguageServer for DjangoLanguageServer { let url_version = self .with_session_mut(|session| { - let Some(url) = - paths::parse_lsp_uri(¶ms.text_document.uri, paths::LspContext::DidOpen) - else { - return None; // Error parsing uri (unlikely), skip processing this document - }; - - let language_id = - djls_workspace::LanguageId::from(params.text_document.language_id.as_str()); + let url = + paths::parse_lsp_uri(¶ms.text_document.uri, paths::LspContext::DidOpen)?; let document = djls_workspace::TextDocument::new( params.text_document.text.clone(), params.text_document.version, - language_id, + LanguageId::from(params.text_document.language_id.as_str()), ); session.open_document(&url, document); + Some((url, params.text_document.version)) }) .await; @@ -242,11 +231,7 @@ impl LanguageServer for DjangoLanguageServer { .with_session_mut(|session| { let url = paths::parse_lsp_uri(¶ms.text_document.uri, paths::LspContext::DidSave)?; - - session.save_document(&url); - - // Get current version from document buffer - let version = session.get_document(&url).map(|doc| doc.version()); + let version = session.save_document(&url).map(|doc| doc.version()); Some((url, version)) }) .await; @@ -260,12 +245,8 @@ impl LanguageServer for DjangoLanguageServer { tracing::info!("Changed document: {:?}", params.text_document.uri); self.with_session_mut(|session| { - let Some(url) = - paths::parse_lsp_uri(¶ms.text_document.uri, paths::LspContext::DidChange) - else { - return None; // Error parsing uri (unlikely), skip processing this change - }; - + let url = + paths::parse_lsp_uri(¶ms.text_document.uri, paths::LspContext::DidChange)?; session.update_document(&url, params.content_changes, params.text_document.version); Some(url) }) @@ -277,12 +258,8 @@ impl LanguageServer for DjangoLanguageServer { let url = self .with_session_mut(|session| { - let Some(url) = - paths::parse_lsp_uri(¶ms.text_document.uri, paths::LspContext::DidClose) - else { - return None; // Error parsing uri (unlikely), skip processing this close - }; - + let url = + paths::parse_lsp_uri(¶ms.text_document.uri, paths::LspContext::DidClose)?; if session.close_document(&url).is_none() { tracing::warn!("Attempted to close document without overlay: {}", url); } @@ -435,10 +412,10 @@ impl LanguageServer for DjangoLanguageServer { tracing::info!("Configuration change detected. Reloading settings..."); self.with_session_mut(|session| { - if let Some(project) = session.project() { - let project_root = project.root(session.database()); + if session.project().is_some() { + let project_root = session.database().project_root_or_cwd(); - match djls_conf::Settings::new(project_root) { + match djls_conf::Settings::new(&project_root) { Ok(new_settings) => { session.set_settings(new_settings); } diff --git a/crates/djls-server/src/session.rs b/crates/djls-server/src/session.rs index e1a98c5c..f3cf3025 100644 --- a/crates/djls-server/src/session.rs +++ b/crates/djls-server/src/session.rs @@ -6,7 +6,6 @@ use camino::Utf8PathBuf; use djls_conf::Settings; use djls_project::Db as ProjectDb; -use djls_project::Interpreter; use djls_source::File; use djls_source::FileKind; use djls_workspace::paths; @@ -14,7 +13,6 @@ use djls_workspace::PositionEncoding; use djls_workspace::TextDocument; use djls_workspace::Workspace; use djls_workspace::WorkspaceFileEvent; -use salsa::Setter; use tower_lsp_server::lsp_types; use url::Url; @@ -65,36 +63,15 @@ impl Session { .and_then(|p| Utf8PathBuf::from_path_buf(p).ok()) }); - let settings = if let Some(path) = &project_path { - djls_conf::Settings::new(path).unwrap_or_else(|_| djls_conf::Settings::default()) - } else { - Settings::default() - }; + let settings = project_path + .as_ref() + .and_then(|path| djls_conf::Settings::new(path).ok()) + .unwrap_or_default(); let workspace = Workspace::new(); - let mut db = DjangoDatabase::new(workspace.file_system()); - if let Some(root_path) = &project_path { - db.set_project(root_path); - - if let Some(project) = db.project() { - // TODO: should this logic live in the project? - if let Some(venv_path) = settings.venv_path() { - let interpreter = Interpreter::VenvPath(venv_path.to_string()); - project.set_interpreter(&mut db).to(interpreter); - } else if let Ok(virtual_env) = std::env::var("VIRTUAL_ENV") { - let interpreter = Interpreter::VenvPath(virtual_env); - project.set_interpreter(&mut db).to(interpreter); - } - - // TODO: allow for configuring via settings - if let Ok(settings_module) = std::env::var("DJANGO_SETTINGS_MODULE") { - project - .set_settings_module(&mut db) - .to(Some(settings_module)); - } - } - } + let mut db = DjangoDatabase::new(workspace.file_system()); + db.set_project(project_path.as_deref(), &settings); Self { settings, @@ -182,10 +159,12 @@ impl Session { } } - pub fn save_document(&mut self, url: &Url) { + pub fn save_document(&mut self, url: &Url) -> Option { if let Some(event) = self.workspace.save_document(&mut self.db, url) { self.handle_file_event(&event); } + + self.workspace.get_document(url) } /// Close a document.