Skip to content

Commit b71dfe5

Browse files
change server session to be an option and create on init (#154)
1 parent 11ef05d commit b71dfe5

File tree

2 files changed

+135
-114
lines changed

2 files changed

+135
-114
lines changed

crates/djls-server/src/server.rs

Lines changed: 110 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::future::Future;
12
use std::sync::Arc;
23

34
use tokio::sync::RwLock;
@@ -32,38 +33,78 @@ const SERVER_NAME: &str = "Django Language Server";
3233
const SERVER_VERSION: &str = "0.1.0";
3334

3435
pub struct DjangoLanguageServer {
35-
session: Arc<RwLock<Session>>,
36+
session: Arc<RwLock<Option<Session>>>,
3637
queue: Queue,
3738
}
3839

3940
impl DjangoLanguageServer {
4041
#[must_use]
4142
pub fn new() -> Self {
4243
Self {
43-
session: Arc::new(RwLock::new(Session::default())),
44+
session: Arc::new(RwLock::new(None)),
4445
queue: Queue::new(),
4546
}
4647
}
4748

48-
pub async fn with_session<R>(&self, f: impl FnOnce(&Session) -> R) -> R {
49+
pub async fn with_session<F, R>(&self, f: F) -> R
50+
where
51+
F: FnOnce(&Session) -> R,
52+
R: Default,
53+
{
4954
let session = self.session.read().await;
50-
f(&session)
55+
if let Some(s) = &*session {
56+
f(s)
57+
} else {
58+
client::log_message(
59+
MessageType::ERROR,
60+
"Attempted to access session before initialization",
61+
);
62+
R::default()
63+
}
5164
}
5265

53-
pub async fn with_session_mut<R>(&self, f: impl FnOnce(&mut Session) -> R) -> R {
66+
pub async fn with_session_mut<F, R>(&self, f: F) -> R
67+
where
68+
F: FnOnce(&mut Session) -> R,
69+
R: Default,
70+
{
5471
let mut session = self.session.write().await;
55-
f(&mut session)
72+
if let Some(s) = &mut *session {
73+
f(s)
74+
} else {
75+
client::log_message(
76+
MessageType::ERROR,
77+
"Attempted to access session before initialization",
78+
);
79+
R::default()
80+
}
81+
}
82+
83+
pub async fn with_session_task<F, Fut>(&self, f: F)
84+
where
85+
F: FnOnce(Arc<RwLock<Option<Session>>>) -> Fut + Send + 'static,
86+
Fut: Future<Output = anyhow::Result<()>> + Send + 'static,
87+
{
88+
let session_arc = Arc::clone(&self.session);
89+
90+
if let Err(e) = self.queue.submit(async move { f(session_arc).await }).await {
91+
client::log_message(MessageType::ERROR, format!("Failed to submit task: {e}"));
92+
} else {
93+
client::log_message(MessageType::INFO, "Task submitted successfully");
94+
}
5695
}
5796
}
5897

5998
impl LanguageServer for DjangoLanguageServer {
6099
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
61100
client::log_message(MessageType::INFO, "Initializing server...");
62101

63-
self.with_session_mut(|session| {
64-
session.set_client_capabilities(params.capabilities);
65-
})
66-
.await;
102+
let session = Session::new(&params);
103+
104+
{
105+
let mut session_lock = self.session.write().await;
106+
*session_lock = Some(session);
107+
}
67108

68109
Ok(InitializeResult {
69110
capabilities: ServerCapabilities {
@@ -109,121 +150,82 @@ impl LanguageServer for DjangoLanguageServer {
109150
"Server received initialized notification.",
110151
);
111152

112-
let init_params = InitializeParams {
113-
// Using the current directory by default right now, but we should switch to
114-
// *falling back* to current dir if workspace folders is empty
115-
workspace_folders: None,
116-
..Default::default()
117-
};
118-
119-
let has_project =
120-
if let Some(project_path) = crate::workspace::get_project_path(&init_params) {
121-
self.with_session_mut(|session| {
122-
let settings = djls_conf::Settings::new(&project_path)
123-
.unwrap_or_else(|_| djls_conf::Settings::default());
124-
session.set_settings(settings);
125-
126-
let project = djls_project::DjangoProject::new(project_path);
127-
session.set_project(project);
128-
129-
true
130-
})
131-
.await
132-
} else {
133-
false
134-
};
135-
136-
if has_project {
137-
client::log_message(
138-
MessageType::INFO,
139-
"Project discovered from current directory",
140-
);
141-
} else {
142-
client::log_message(
143-
MessageType::INFO,
144-
"No project discovered; running without project context",
145-
);
146-
}
147-
148-
let session_arc = Arc::clone(&self.session);
149-
150-
if let Err(e) = self
151-
.queue
152-
.submit(async move {
153-
let project_path_and_venv = {
154-
let session = session_arc.read().await;
155-
session.project().map(|p| {
153+
self.with_session_task(|session_arc| async move {
154+
let project_path_and_venv = {
155+
let session_lock = session_arc.read().await;
156+
match &*session_lock {
157+
Some(session) => session.project().map(|p| {
156158
(
157159
p.path().display().to_string(),
158-
session.settings().venv_path().map(std::string::ToString::to_string),
160+
session
161+
.settings()
162+
.venv_path()
163+
.map(std::string::ToString::to_string),
159164
)
160-
})
161-
};
165+
}),
166+
None => None,
167+
}
168+
};
169+
170+
if let Some((path_display, venv_path)) = project_path_and_venv {
171+
client::log_message(
172+
MessageType::INFO,
173+
format!("Task: Starting initialization for project at: {path_display}"),
174+
);
162175

163-
if let Some((path_display, venv_path)) = project_path_and_venv {
176+
if let Some(ref path) = venv_path {
164177
client::log_message(
165178
MessageType::INFO,
166-
format!(
167-
"Task: Starting initialization for project at: {path_display}"
168-
),
179+
format!("Using virtual environment from config: {path}"),
169180
);
181+
}
182+
183+
let init_result = {
184+
let mut session_lock = session_arc.write().await;
185+
match &mut *session_lock {
186+
Some(session) => {
187+
if let Some(project) = session.project_mut().as_mut() {
188+
project.initialize(venv_path.as_deref())
189+
} else {
190+
// Project was removed between read and write locks
191+
Ok(())
192+
}
193+
}
194+
None => Ok(()),
195+
}
196+
};
170197

171-
if let Some(ref path) = venv_path {
198+
match init_result {
199+
Ok(()) => {
172200
client::log_message(
173201
MessageType::INFO,
174-
format!("Using virtual environment from config: {path}"),
202+
format!("Task: Successfully initialized project: {path_display}"),
175203
);
176204
}
205+
Err(e) => {
206+
client::log_message(
207+
MessageType::ERROR,
208+
format!(
209+
"Task: Failed to initialize Django project at {path_display}: {e}"
210+
),
211+
);
177212

178-
let init_result = {
179-
let mut session = session_arc.write().await;
180-
if let Some(project) = session.project_mut().as_mut() {
181-
project.initialize(venv_path.as_deref())
182-
} else {
183-
// Project was removed between read and write locks
184-
Ok(())
185-
}
186-
};
187-
188-
match init_result {
189-
Ok(()) => {
190-
client::log_message(
191-
MessageType::INFO,
192-
format!(
193-
"Task: Successfully initialized project: {path_display}"
194-
),
195-
);
196-
}
197-
Err(e) => {
198-
client::log_message(
199-
MessageType::ERROR,
200-
format!(
201-
"Task: Failed to initialize Django project at {path_display}: {e}"
202-
),
203-
);
204-
205-
// Clear project on error
206-
let mut session = session_arc.write().await;
213+
// Clear project on error
214+
let mut session_lock = session_arc.write().await;
215+
if let Some(session) = &mut *session_lock {
207216
*session.project_mut() = None;
208217
}
209218
}
210-
} else {
211-
client::log_message(
212-
MessageType::INFO,
213-
"Task: No project instance found to initialize.",
214-
);
215219
}
216-
Ok(())
217-
})
218-
.await
219-
{
220-
client::log_message(
221-
MessageType::ERROR,
222-
format!("Failed to submit project initialization task: {e}"),
223-
);
224-
} else {
225-
client::log_message(MessageType::INFO, "Scheduled project initialization task.");
226-
}
220+
} else {
221+
client::log_message(
222+
MessageType::INFO,
223+
"Task: No project instance found to initialize.",
224+
);
225+
}
226+
Ok(())
227+
})
228+
.await;
227229
}
228230

229231
async fn shutdown(&self) -> LspResult<()> {

crates/djls-server/src/session.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ use djls_conf::Settings;
22
use djls_project::DjangoProject;
33
use salsa::StorageHandle;
44
use tower_lsp_server::lsp_types::ClientCapabilities;
5+
use tower_lsp_server::lsp_types::InitializeParams;
56

67
use crate::db::ServerDatabase;
78
use crate::documents::Store;
89

910
#[derive(Default)]
1011
pub struct Session {
11-
client_capabilities: Option<ClientCapabilities>,
1212
project: Option<DjangoProject>,
1313
documents: Store,
1414
settings: Settings,
1515

16+
#[allow(dead_code)]
17+
client_capabilities: ClientCapabilities,
18+
1619
/// A thread-safe Salsa database handle that can be shared between threads.
1720
///
1821
/// This implements the insight from [this Salsa Zulip discussion](https://salsa.zulipchat.com/#narrow/channel/145099-Using-Salsa/topic/.E2.9C.94.20Advice.20on.20using.20salsa.20from.20Sync.20.2B.20Send.20context/with/495497515)
@@ -45,8 +48,27 @@ pub struct Session {
4548
}
4649

4750
impl Session {
48-
pub fn set_client_capabilities(&mut self, client_capabilities: ClientCapabilities) {
49-
self.client_capabilities = Some(client_capabilities);
51+
pub fn new(params: &InitializeParams) -> Self {
52+
let project_path = crate::workspace::get_project_path(params);
53+
54+
let (project, settings) = if let Some(path) = &project_path {
55+
let settings =
56+
djls_conf::Settings::new(path).unwrap_or_else(|_| djls_conf::Settings::default());
57+
58+
let project = Some(djls_project::DjangoProject::new(path.clone()));
59+
60+
(project, settings)
61+
} else {
62+
(None, Settings::default())
63+
};
64+
65+
Self {
66+
client_capabilities: params.capabilities.clone(),
67+
project,
68+
documents: Store::default(),
69+
settings,
70+
db_handle: StorageHandle::new(None),
71+
}
5072
}
5173

5274
pub fn project(&self) -> Option<&DjangoProject> {
@@ -56,9 +78,6 @@ impl Session {
5678
pub fn project_mut(&mut self) -> &mut Option<DjangoProject> {
5779
&mut self.project
5880
}
59-
pub fn set_project(&mut self, project: DjangoProject) {
60-
self.project = Some(project);
61-
}
6281

6382
pub fn documents(&self) -> &Store {
6483
&self.documents

0 commit comments

Comments
 (0)