1use crate::types::{LspError, LspResult};
26use serde_json::Value;
27use std::sync::Arc;
28use tracing::{debug, info, warn};
29
30pub struct LspProxy {
34 external_lsp: Option<Arc<dyn ExternalLspClient>>,
36 enable_fallback: bool,
38}
39
40pub trait ExternalLspClient: Send + Sync {
42 fn forward_completion(
44 &self,
45 language: &str,
46 uri: &str,
47 position: Value,
48 context: Value,
49 ) -> LspResult<Option<Value>>;
50
51 fn forward_diagnostics(
53 &self,
54 language: &str,
55 uri: &str,
56 ) -> LspResult<Option<Value>>;
57
58 fn forward_hover(
60 &self,
61 language: &str,
62 uri: &str,
63 position: Value,
64 ) -> LspResult<Option<Value>>;
65
66 fn forward_definition(
68 &self,
69 language: &str,
70 uri: &str,
71 position: Value,
72 ) -> LspResult<Option<Value>>;
73
74 fn forward_references(
76 &self,
77 language: &str,
78 uri: &str,
79 position: Value,
80 ) -> LspResult<Option<Value>>;
81
82 fn is_available(&self, language: &str) -> bool;
84}
85
86impl LspProxy {
87 pub fn new() -> Self {
89 Self {
90 external_lsp: None,
91 enable_fallback: true,
92 }
93 }
94
95 pub fn with_external_lsp(
97 external_lsp: Arc<dyn ExternalLspClient>,
98 enable_fallback: bool,
99 ) -> Self {
100 Self {
101 external_lsp: Some(external_lsp),
102 enable_fallback,
103 }
104 }
105
106 pub fn route_completion<F>(
120 &self,
121 language: &str,
122 uri: &str,
123 position: Value,
124 context: Value,
125 fallback_fn: F,
126 ) -> LspResult<Value>
127 where
128 F: FnOnce() -> LspResult<Value>,
129 {
130 if let Some(external_lsp) = &self.external_lsp {
132 if external_lsp.is_available(language) {
133 debug!("Routing completion to external LSP for language: {}", language);
134 match external_lsp.forward_completion(language, uri, position, context) {
135 Ok(Some(result)) => {
136 info!("Received completion from external LSP");
137 return Ok(result);
138 }
139 Ok(None) => {
140 debug!("External LSP returned no completions");
141 }
142 Err(e) => {
143 warn!("External LSP completion failed: {}", e);
144 if !self.enable_fallback {
145 return Err(e);
146 }
147 }
148 }
149 }
150 }
151
152 if self.enable_fallback {
154 debug!("Falling back to internal completion provider");
155 fallback_fn()
156 } else {
157 Err(LspError::InternalError(
158 "External LSP unavailable and fallback disabled".to_string(),
159 ))
160 }
161 }
162
163 pub fn route_diagnostics<F>(
175 &self,
176 language: &str,
177 uri: &str,
178 fallback_fn: F,
179 ) -> LspResult<Value>
180 where
181 F: FnOnce() -> LspResult<Value>,
182 {
183 if let Some(external_lsp) = &self.external_lsp {
185 if external_lsp.is_available(language) {
186 debug!("Routing diagnostics to external LSP for language: {}", language);
187 match external_lsp.forward_diagnostics(language, uri) {
188 Ok(Some(result)) => {
189 info!("Received diagnostics from external LSP");
190 return Ok(result);
191 }
192 Ok(None) => {
193 debug!("External LSP returned no diagnostics");
194 }
195 Err(e) => {
196 warn!("External LSP diagnostics failed: {}", e);
197 if !self.enable_fallback {
198 return Err(e);
199 }
200 }
201 }
202 }
203 }
204
205 if self.enable_fallback {
207 debug!("Falling back to internal diagnostics provider");
208 fallback_fn()
209 } else {
210 Err(LspError::InternalError(
211 "External LSP unavailable and fallback disabled".to_string(),
212 ))
213 }
214 }
215
216 pub fn route_hover<F>(
229 &self,
230 language: &str,
231 uri: &str,
232 position: Value,
233 fallback_fn: F,
234 ) -> LspResult<Value>
235 where
236 F: FnOnce() -> LspResult<Value>,
237 {
238 if let Some(external_lsp) = &self.external_lsp {
240 if external_lsp.is_available(language) {
241 debug!("Routing hover to external LSP for language: {}", language);
242 match external_lsp.forward_hover(language, uri, position) {
243 Ok(Some(result)) => {
244 info!("Received hover from external LSP");
245 return Ok(result);
246 }
247 Ok(None) => {
248 debug!("External LSP returned no hover information");
249 }
250 Err(e) => {
251 warn!("External LSP hover failed: {}", e);
252 if !self.enable_fallback {
253 return Err(e);
254 }
255 }
256 }
257 }
258 }
259
260 if self.enable_fallback {
262 debug!("Falling back to internal hover provider");
263 fallback_fn()
264 } else {
265 Err(LspError::InternalError(
266 "External LSP unavailable and fallback disabled".to_string(),
267 ))
268 }
269 }
270
271 pub fn route_definition<F>(
284 &self,
285 language: &str,
286 uri: &str,
287 position: Value,
288 fallback_fn: F,
289 ) -> LspResult<Value>
290 where
291 F: FnOnce() -> LspResult<Value>,
292 {
293 if let Some(external_lsp) = &self.external_lsp {
295 if external_lsp.is_available(language) {
296 debug!("Routing definition to external LSP for language: {}", language);
297 match external_lsp.forward_definition(language, uri, position) {
298 Ok(Some(result)) => {
299 info!("Received definition from external LSP");
300 return Ok(result);
301 }
302 Ok(None) => {
303 debug!("External LSP returned no definition");
304 }
305 Err(e) => {
306 warn!("External LSP definition failed: {}", e);
307 if !self.enable_fallback {
308 return Err(e);
309 }
310 }
311 }
312 }
313 }
314
315 if self.enable_fallback {
317 debug!("Falling back to internal definition provider");
318 fallback_fn()
319 } else {
320 Err(LspError::InternalError(
321 "External LSP unavailable and fallback disabled".to_string(),
322 ))
323 }
324 }
325
326 pub fn route_references<F>(
339 &self,
340 language: &str,
341 uri: &str,
342 position: Value,
343 fallback_fn: F,
344 ) -> LspResult<Value>
345 where
346 F: FnOnce() -> LspResult<Value>,
347 {
348 if let Some(external_lsp) = &self.external_lsp {
350 if external_lsp.is_available(language) {
351 debug!("Routing references to external LSP for language: {}", language);
352 match external_lsp.forward_references(language, uri, position) {
353 Ok(Some(result)) => {
354 info!("Received references from external LSP");
355 return Ok(result);
356 }
357 Ok(None) => {
358 debug!("External LSP returned no references");
359 }
360 Err(e) => {
361 warn!("External LSP references failed: {}", e);
362 if !self.enable_fallback {
363 return Err(e);
364 }
365 }
366 }
367 }
368 }
369
370 if self.enable_fallback {
372 debug!("Falling back to internal references provider");
373 fallback_fn()
374 } else {
375 Err(LspError::InternalError(
376 "External LSP unavailable and fallback disabled".to_string(),
377 ))
378 }
379 }
380
381 pub fn is_external_lsp_available(&self, language: &str) -> bool {
383 self.external_lsp
384 .as_ref()
385 .map(|lsp| lsp.is_available(language))
386 .unwrap_or(false)
387 }
388}
389
390impl Default for LspProxy {
391 fn default() -> Self {
392 Self::new()
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn test_proxy_creation() {
402 let proxy = LspProxy::new();
403 assert!(!proxy.is_external_lsp_available("rust"));
404 }
405
406 #[test]
407 fn test_proxy_fallback() {
408 let proxy = LspProxy::new();
409 let result = proxy.route_completion(
410 "rust",
411 "file:///test.rs",
412 Value::Null,
413 Value::Null,
414 || Ok(Value::Array(vec![])),
415 );
416 assert!(result.is_ok());
417 }
418}