Skip to content

Commit c43baae

Browse files
Refactor and tighten up Project bootstrapping and Session setup (#236)
1 parent 01e52a4 commit c43baae

File tree

5 files changed

+72
-101
lines changed

5 files changed

+72
-101
lines changed

crates/djls-project/src/db.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
1313
use std::sync::Arc;
1414

15-
use camino::Utf8Path;
15+
use camino::Utf8PathBuf;
1616

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

29-
/// Get the project root path if a project is set
30-
fn project_path(&self) -> Option<&Utf8Path> {
31-
self.project().map(|p| p.root(self).as_path())
29+
/// Return the current project root or fall back to the current working directory.
30+
fn project_root_or_cwd(&self) -> Utf8PathBuf {
31+
if let Some(project) = self.project() {
32+
project.root(self).clone()
33+
} else if let Ok(current_dir) = std::env::current_dir() {
34+
Utf8PathBuf::from_path_buf(current_dir).unwrap_or_else(|_| Utf8PathBuf::from("."))
35+
} else {
36+
Utf8PathBuf::from(".")
37+
}
3238
}
3339
}

crates/djls-project/src/project.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use camino::Utf8Path;
12
use camino::Utf8PathBuf;
23

34
use crate::db::Db as ProjectDb;
@@ -26,6 +27,24 @@ pub struct Project {
2627
}
2728

2829
impl Project {
30+
pub fn bootstrap(
31+
db: &dyn ProjectDb,
32+
root: &Utf8Path,
33+
venv_path: Option<&str>,
34+
settings_module: Option<&str>,
35+
) -> Project {
36+
let interpreter = venv_path
37+
.map(|path| Interpreter::VenvPath(path.to_string()))
38+
.or_else(|| std::env::var("VIRTUAL_ENV").ok().map(Interpreter::VenvPath))
39+
.unwrap_or(Interpreter::Auto);
40+
41+
let django_settings = settings_module
42+
.map(std::string::ToString::to_string)
43+
.or_else(|| std::env::var("DJANGO_SETTINGS_MODULE").ok());
44+
45+
Project::new(db, root.to_path_buf(), interpreter, django_settings)
46+
}
47+
2948
pub fn initialize(self, db: &dyn ProjectDb) {
3049
let _ = django_available(db, self);
3150
let _ = django_settings_module(db, self);

crates/djls-server/src/db.rs

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ use std::sync::Arc;
88
use std::sync::Mutex;
99

1010
use camino::Utf8Path;
11-
use camino::Utf8PathBuf;
11+
use djls_conf::Settings;
1212
use djls_project::Db as ProjectDb;
1313
use djls_project::InspectorPool;
14-
use djls_project::Interpreter;
1514
use djls_project::Project;
1615
use djls_semantic::Db as SemanticDb;
1716
use djls_semantic::TagSpecs;
@@ -75,19 +74,6 @@ impl Default for DjangoDatabase {
7574
}
7675

7776
impl DjangoDatabase {
78-
/// Set the project for this database instance
79-
///
80-
/// # Panics
81-
///
82-
/// Panics if the project mutex is poisoned.
83-
pub fn set_project(&self, root: &Utf8Path) {
84-
let interpreter = Interpreter::Auto;
85-
let django_settings = std::env::var("DJANGO_SETTINGS_MODULE").ok();
86-
87-
let project = Project::new(self, root.to_path_buf(), interpreter, django_settings);
88-
89-
*self.project.lock().unwrap() = Some(project);
90-
}
9177
/// Create a new [`DjangoDatabase`] with the given file system handle.
9278
pub fn new(file_system: Arc<dyn FileSystem>) -> Self {
9379
Self {
@@ -99,6 +85,18 @@ impl DjangoDatabase {
9985
logs: Arc::new(Mutex::new(None)),
10086
}
10187
}
88+
89+
/// Set the project for this database instance
90+
///
91+
/// # Panics
92+
///
93+
/// Panics if the project mutex is poisoned.
94+
pub fn set_project(&mut self, root: Option<&Utf8Path>, settings: &Settings) {
95+
if let Some(path) = root {
96+
let project = Project::bootstrap(self, path, settings.venv_path(), None);
97+
*self.project.lock().unwrap() = Some(project);
98+
}
99+
}
102100
}
103101

104102
#[salsa::db]
@@ -124,19 +122,11 @@ impl TemplateDb for DjangoDatabase {}
124122
#[salsa::db]
125123
impl SemanticDb for DjangoDatabase {
126124
fn tag_specs(&self) -> Arc<TagSpecs> {
127-
let project_root = if let Some(project) = self.project() {
128-
project.root(self).clone()
129-
} else {
130-
std::env::current_dir()
131-
.ok()
132-
.and_then(|p| Utf8PathBuf::from_path_buf(p).ok())
133-
.unwrap_or_else(|| Utf8PathBuf::from("."))
134-
};
125+
let project_root = self.project_root_or_cwd();
135126

136-
let tag_specs = if let Ok(settings) = djls_conf::Settings::new(&project_root) {
137-
TagSpecs::from(&settings)
138-
} else {
139-
djls_semantic::django_builtin_specs()
127+
let tag_specs = match djls_conf::Settings::new(&project_root) {
128+
Ok(settings) => TagSpecs::from(&settings),
129+
Err(_) => djls_semantic::django_builtin_specs(),
140130
};
141131

142132
Arc::new(tag_specs)

crates/djls-server/src/server.rs

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use djls_project::Db as ProjectDb;
66
use djls_semantic::Db as SemanticDb;
77
use djls_source::FileKind;
88
use djls_workspace::paths;
9+
use djls_workspace::LanguageId;
910
use tokio::sync::Mutex;
1011
use tower_lsp_server::jsonrpc::Result as LspResult;
1112
use tower_lsp_server::lsp_types;
@@ -179,19 +180,12 @@ impl LanguageServer for DjangoLanguageServer {
179180
tracing::info!("Server received initialized notification.");
180181

181182
self.with_session_task(move |session_arc| async move {
182-
let session_lock = session_arc.lock().await;
183+
let session = session_arc.lock().await;
183184

184-
let project_path = session_lock
185-
.project()
186-
.map(|p| p.root(session_lock.database()).clone());
187-
188-
if let Some(path) = project_path {
185+
if let Some(project) = session.project() {
186+
let path = project.root(session.database()).clone();
189187
tracing::info!("Task: Starting initialization for project at: {}", path);
190-
191-
if let Some(project) = session_lock.project() {
192-
project.initialize(session_lock.database());
193-
}
194-
188+
project.initialize(session.database());
195189
tracing::info!("Task: Successfully initialized project: {}", path);
196190
} else {
197191
tracing::info!("Task: No project configured, skipping initialization.");
@@ -211,21 +205,16 @@ impl LanguageServer for DjangoLanguageServer {
211205

212206
let url_version = self
213207
.with_session_mut(|session| {
214-
let Some(url) =
215-
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidOpen)
216-
else {
217-
return None; // Error parsing uri (unlikely), skip processing this document
218-
};
219-
220-
let language_id =
221-
djls_workspace::LanguageId::from(params.text_document.language_id.as_str());
208+
let url =
209+
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidOpen)?;
222210
let document = djls_workspace::TextDocument::new(
223211
params.text_document.text.clone(),
224212
params.text_document.version,
225-
language_id,
213+
LanguageId::from(params.text_document.language_id.as_str()),
226214
);
227215

228216
session.open_document(&url, document);
217+
229218
Some((url, params.text_document.version))
230219
})
231220
.await;
@@ -242,11 +231,7 @@ impl LanguageServer for DjangoLanguageServer {
242231
.with_session_mut(|session| {
243232
let url =
244233
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidSave)?;
245-
246-
session.save_document(&url);
247-
248-
// Get current version from document buffer
249-
let version = session.get_document(&url).map(|doc| doc.version());
234+
let version = session.save_document(&url).map(|doc| doc.version());
250235
Some((url, version))
251236
})
252237
.await;
@@ -260,12 +245,8 @@ impl LanguageServer for DjangoLanguageServer {
260245
tracing::info!("Changed document: {:?}", params.text_document.uri);
261246

262247
self.with_session_mut(|session| {
263-
let Some(url) =
264-
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidChange)
265-
else {
266-
return None; // Error parsing uri (unlikely), skip processing this change
267-
};
268-
248+
let url =
249+
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidChange)?;
269250
session.update_document(&url, params.content_changes, params.text_document.version);
270251
Some(url)
271252
})
@@ -277,12 +258,8 @@ impl LanguageServer for DjangoLanguageServer {
277258

278259
let url = self
279260
.with_session_mut(|session| {
280-
let Some(url) =
281-
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidClose)
282-
else {
283-
return None; // Error parsing uri (unlikely), skip processing this close
284-
};
285-
261+
let url =
262+
paths::parse_lsp_uri(&params.text_document.uri, paths::LspContext::DidClose)?;
286263
if session.close_document(&url).is_none() {
287264
tracing::warn!("Attempted to close document without overlay: {}", url);
288265
}
@@ -435,10 +412,10 @@ impl LanguageServer for DjangoLanguageServer {
435412
tracing::info!("Configuration change detected. Reloading settings...");
436413

437414
self.with_session_mut(|session| {
438-
if let Some(project) = session.project() {
439-
let project_root = project.root(session.database());
415+
if session.project().is_some() {
416+
let project_root = session.database().project_root_or_cwd();
440417

441-
match djls_conf::Settings::new(project_root) {
418+
match djls_conf::Settings::new(&project_root) {
442419
Ok(new_settings) => {
443420
session.set_settings(new_settings);
444421
}

crates/djls-server/src/session.rs

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@
66
use camino::Utf8PathBuf;
77
use djls_conf::Settings;
88
use djls_project::Db as ProjectDb;
9-
use djls_project::Interpreter;
109
use djls_source::File;
1110
use djls_source::FileKind;
1211
use djls_workspace::paths;
1312
use djls_workspace::PositionEncoding;
1413
use djls_workspace::TextDocument;
1514
use djls_workspace::Workspace;
1615
use djls_workspace::WorkspaceFileEvent;
17-
use salsa::Setter;
1816
use tower_lsp_server::lsp_types;
1917
use url::Url;
2018

@@ -65,36 +63,15 @@ impl Session {
6563
.and_then(|p| Utf8PathBuf::from_path_buf(p).ok())
6664
});
6765

68-
let settings = if let Some(path) = &project_path {
69-
djls_conf::Settings::new(path).unwrap_or_else(|_| djls_conf::Settings::default())
70-
} else {
71-
Settings::default()
72-
};
66+
let settings = project_path
67+
.as_ref()
68+
.and_then(|path| djls_conf::Settings::new(path).ok())
69+
.unwrap_or_default();
7370

7471
let workspace = Workspace::new();
75-
let mut db = DjangoDatabase::new(workspace.file_system());
7672

77-
if let Some(root_path) = &project_path {
78-
db.set_project(root_path);
79-
80-
if let Some(project) = db.project() {
81-
// TODO: should this logic live in the project?
82-
if let Some(venv_path) = settings.venv_path() {
83-
let interpreter = Interpreter::VenvPath(venv_path.to_string());
84-
project.set_interpreter(&mut db).to(interpreter);
85-
} else if let Ok(virtual_env) = std::env::var("VIRTUAL_ENV") {
86-
let interpreter = Interpreter::VenvPath(virtual_env);
87-
project.set_interpreter(&mut db).to(interpreter);
88-
}
89-
90-
// TODO: allow for configuring via settings
91-
if let Ok(settings_module) = std::env::var("DJANGO_SETTINGS_MODULE") {
92-
project
93-
.set_settings_module(&mut db)
94-
.to(Some(settings_module));
95-
}
96-
}
97-
}
73+
let mut db = DjangoDatabase::new(workspace.file_system());
74+
db.set_project(project_path.as_deref(), &settings);
9875

9976
Self {
10077
settings,
@@ -182,10 +159,12 @@ impl Session {
182159
}
183160
}
184161

185-
pub fn save_document(&mut self, url: &Url) {
162+
pub fn save_document(&mut self, url: &Url) -> Option<TextDocument> {
186163
if let Some(event) = self.workspace.save_document(&mut self.db, url) {
187164
self.handle_file_event(&event);
188165
}
166+
167+
self.workspace.get_document(url)
189168
}
190169

191170
/// Close a document.

0 commit comments

Comments
 (0)