|
| 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