|
| 1 | +use std::future::Future; |
1 | 2 | use std::sync::Arc; |
2 | 3 |
|
3 | 4 | use tokio::sync::RwLock; |
@@ -32,38 +33,78 @@ const SERVER_NAME: &str = "Django Language Server"; |
32 | 33 | const SERVER_VERSION: &str = "0.1.0"; |
33 | 34 |
|
34 | 35 | pub struct DjangoLanguageServer { |
35 | | - session: Arc<RwLock<Session>>, |
| 36 | + session: Arc<RwLock<Option<Session>>>, |
36 | 37 | queue: Queue, |
37 | 38 | } |
38 | 39 |
|
39 | 40 | impl DjangoLanguageServer { |
40 | 41 | #[must_use] |
41 | 42 | pub fn new() -> Self { |
42 | 43 | Self { |
43 | | - session: Arc::new(RwLock::new(Session::default())), |
| 44 | + session: Arc::new(RwLock::new(None)), |
44 | 45 | queue: Queue::new(), |
45 | 46 | } |
46 | 47 | } |
47 | 48 |
|
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 | + { |
49 | 54 | 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 | + } |
51 | 64 | } |
52 | 65 |
|
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 | + { |
54 | 71 | 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 | + } |
56 | 95 | } |
57 | 96 | } |
58 | 97 |
|
59 | 98 | impl LanguageServer for DjangoLanguageServer { |
60 | 99 | async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> { |
61 | 100 | client::log_message(MessageType::INFO, "Initializing server..."); |
62 | 101 |
|
63 | | - self.with_session_mut(|session| { |
64 | | - session.set_client_capabilities(params.capabilities); |
65 | | - }) |
66 | | - .await; |
| 102 | + let session = Session::new(¶ms); |
| 103 | + |
| 104 | + { |
| 105 | + let mut session_lock = self.session.write().await; |
| 106 | + *session_lock = Some(session); |
| 107 | + } |
67 | 108 |
|
68 | 109 | Ok(InitializeResult { |
69 | 110 | capabilities: ServerCapabilities { |
@@ -109,121 +150,82 @@ impl LanguageServer for DjangoLanguageServer { |
109 | 150 | "Server received initialized notification.", |
110 | 151 | ); |
111 | 152 |
|
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| { |
156 | 158 | ( |
157 | 159 | 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), |
159 | 164 | ) |
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 | + ); |
162 | 175 |
|
163 | | - if let Some((path_display, venv_path)) = project_path_and_venv { |
| 176 | + if let Some(ref path) = venv_path { |
164 | 177 | client::log_message( |
165 | 178 | MessageType::INFO, |
166 | | - format!( |
167 | | - "Task: Starting initialization for project at: {path_display}" |
168 | | - ), |
| 179 | + format!("Using virtual environment from config: {path}"), |
169 | 180 | ); |
| 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 | + }; |
170 | 197 |
|
171 | | - if let Some(ref path) = venv_path { |
| 198 | + match init_result { |
| 199 | + Ok(()) => { |
172 | 200 | client::log_message( |
173 | 201 | MessageType::INFO, |
174 | | - format!("Using virtual environment from config: {path}"), |
| 202 | + format!("Task: Successfully initialized project: {path_display}"), |
175 | 203 | ); |
176 | 204 | } |
| 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 | + ); |
177 | 212 |
|
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 { |
207 | 216 | *session.project_mut() = None; |
208 | 217 | } |
209 | 218 | } |
210 | | - } else { |
211 | | - client::log_message( |
212 | | - MessageType::INFO, |
213 | | - "Task: No project instance found to initialize.", |
214 | | - ); |
215 | 219 | } |
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; |
227 | 229 | } |
228 | 230 |
|
229 | 231 | async fn shutdown(&self) -> LspResult<()> { |
|
0 commit comments