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
14 changes: 10 additions & 4 deletions crates/djls-project/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

use std::sync::Arc;

use camino::Utf8Path;
use camino::Utf8PathBuf;

use crate::inspector::pool::InspectorPool;
use crate::project::Project;
Expand All @@ -26,8 +26,14 @@ pub trait Db: salsa::Database {
/// Get the shared inspector pool for executing Python queries
fn inspector_pool(&self) -> Arc<InspectorPool>;

/// 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(".")
}
}
}
19 changes: 19 additions & 0 deletions crates/djls-project/src/project.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use camino::Utf8Path;
use camino::Utf8PathBuf;

use crate::db::Db as ProjectDb;
Expand Down Expand Up @@ -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);
Expand Down
44 changes: 17 additions & 27 deletions crates/djls-server/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<dyn FileSystem>) -> Self {
Self {
Expand All @@ -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]
Expand All @@ -124,19 +122,11 @@ impl TemplateDb for DjangoDatabase {}
#[salsa::db]
impl SemanticDb for DjangoDatabase {
fn tag_specs(&self) -> Arc<TagSpecs> {
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)
Expand Down
57 changes: 17 additions & 40 deletions crates/djls-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.");
Expand All @@ -211,21 +205,16 @@ impl LanguageServer for DjangoLanguageServer {

let url_version = self
.with_session_mut(|session| {
let Some(url) =
paths::parse_lsp_uri(&params.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(&params.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;
Expand All @@ -242,11 +231,7 @@ impl LanguageServer for DjangoLanguageServer {
.with_session_mut(|session| {
let url =
paths::parse_lsp_uri(&params.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;
Expand All @@ -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(&params.text_document.uri, paths::LspContext::DidChange)
else {
return None; // Error parsing uri (unlikely), skip processing this change
};

let url =
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidChange)?;
session.update_document(&url, params.content_changes, params.text_document.version);
Some(url)
})
Expand All @@ -277,12 +258,8 @@ impl LanguageServer for DjangoLanguageServer {

let url = self
.with_session_mut(|session| {
let Some(url) =
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidClose)
else {
return None; // Error parsing uri (unlikely), skip processing this close
};

let url =
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidClose)?;
if session.close_document(&url).is_none() {
tracing::warn!("Attempted to close document without overlay: {}", url);
}
Expand Down Expand Up @@ -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);
}
Expand Down
39 changes: 9 additions & 30 deletions crates/djls-server/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
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;
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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -182,10 +159,12 @@ impl Session {
}
}

pub fn save_document(&mut self, url: &Url) {
pub fn save_document(&mut self, url: &Url) -> Option<TextDocument> {
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.
Expand Down