Skip to main content

cgraph/fetch/
lsp.rs

1//! Minimal stdio LSP client used by the fetch layer.
2//!
3//! The transport is implemented locally because `tower-lsp` supplies protocol
4//! types and server infrastructure, but cgraph needs to act as an LSP client.
5
6mod symbol_names;
7
8use std::{
9    collections::HashMap,
10    ffi::{OsStr, OsString},
11    fmt,
12    path::{Path, PathBuf},
13    process::Stdio,
14    sync::Arc,
15    time::Duration,
16};
17
18use anyhow::{Context, Result, bail};
19use serde::{Serialize, de::DeserializeOwned};
20use serde_json::{Value, json};
21use tokio::{
22    io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader},
23    process::{Child, Command},
24    sync::{Mutex, OnceCell, mpsc, oneshot},
25    task::JoinHandle,
26    time::timeout,
27};
28use tower_lsp::lsp_types::{
29    CallHierarchyClientCapabilities, CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams,
30    CallHierarchyItem, CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams,
31    CallHierarchyPrepareParams, ClientCapabilities, ClientInfo, DocumentSymbol,
32    DocumentSymbolClientCapabilities, DocumentSymbolParams, DocumentSymbolResponse,
33    GeneralClientCapabilities, InitializeParams, InitializeResult, Location, NumberOrString, OneOf,
34    PartialResultParams, Position, PositionEncodingKind, ProgressParams, ProgressParamsValue,
35    Range, ServerInfo, SymbolInformation, SymbolKind, TextDocumentClientCapabilities,
36    TextDocumentIdentifier, TextDocumentPositionParams, TypeHierarchyClientCapabilities,
37    TypeHierarchyItem, TypeHierarchyPrepareParams, TypeHierarchySubtypesParams,
38    TypeHierarchySupertypesParams, Url, WindowClientCapabilities, WorkDoneProgress,
39    WorkDoneProgressParams, WorkspaceClientCapabilities, WorkspaceFolder,
40    WorkspaceSymbolClientCapabilities, WorkspaceSymbolParams, WorkspaceSymbolResponse,
41    request::{
42        CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
43        DocumentSymbolRequest, Initialize, Request, Shutdown, TypeHierarchyPrepare,
44        TypeHierarchySubtypes, TypeHierarchySupertypes, WorkspaceSymbolRequest,
45    },
46};
47
48use crate::{
49    fetch::{FetchSource, HierarchyQuery, HierarchyResponse},
50    state::{HierarchyDirection, HierarchyKind, SourceLocation, SymbolIdentity},
51};
52use symbol_names::SymbolNameAdapter;
53
54pub use crate::fetch::WorkspaceSymbolMatch;
55
56// A corrupt Content-Length must not turn into an attacker-controlled allocation.
57const MAX_MESSAGE_SIZE: usize = 16 * 1024 * 1024;
58const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(2);
59
60#[derive(Clone, Debug)]
61pub struct LspConfig {
62    pub program: OsString,
63    pub args: Vec<OsString>,
64    pub workspace_root: PathBuf,
65    pub initialization_options: Option<Value>,
66}
67
68impl LspConfig {
69    pub fn new(program: impl Into<OsString>, workspace_root: impl Into<PathBuf>) -> Self {
70        Self {
71            program: program.into(),
72            args: Vec::new(),
73            workspace_root: workspace_root.into(),
74            initialization_options: None,
75        }
76    }
77
78    pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
79        self.args.push(arg.into());
80        self
81    }
82
83    pub fn args<I, S>(mut self, args: I) -> Self
84    where
85        I: IntoIterator<Item = S>,
86        S: Into<OsString>,
87    {
88        self.args.extend(args.into_iter().map(Into::into));
89        self
90    }
91
92    pub fn initialization_options(mut self, options: Value) -> Self {
93        self.initialization_options = Some(options);
94        self
95    }
96}
97
98#[derive(Clone, Debug, Eq, PartialEq)]
99/// A provider-level status event, kept separate from individual request results.
100///
101/// Language servers may run several work-done tasks concurrently. The JSON-RPC
102/// actor collapses those protocol tokens into the most useful current update;
103/// the TUI then maps this LSP-specific type into its backend-neutral status.
104pub enum LspStatusUpdate {
105    Ready {
106        message: Option<String>,
107    },
108    Progress {
109        title: String,
110        message: Option<String>,
111        percentage: Option<u32>,
112    },
113    Warning(String),
114    Error(String),
115    Disconnected(String),
116}
117
118#[derive(Clone, Debug)]
119struct ActiveProgress {
120    sequence: u64,
121    title: String,
122    message: Option<String>,
123    percentage: Option<u32>,
124}
125
126#[derive(Default)]
127struct LspProgressTracker {
128    next_sequence: u64,
129    active: HashMap<String, ActiveProgress>,
130}
131
132#[derive(Clone)]
133pub struct WorkspaceSymbolClient {
134    client: JsonRpcClient,
135    workspace_root: PathBuf,
136    symbol_names: SymbolNameAdapter,
137}
138
139impl fmt::Debug for WorkspaceSymbolClient {
140    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
141        formatter
142            .debug_struct("WorkspaceSymbolClient")
143            .finish_non_exhaustive()
144    }
145}
146
147impl WorkspaceSymbolClient {
148    pub async fn query(&self, query: &str) -> Result<Vec<WorkspaceSymbolMatch>> {
149        let params = WorkspaceSymbolParams {
150            query: query.to_owned(),
151            ..WorkspaceSymbolParams::default()
152        };
153        let response: Option<WorkspaceSymbolResponse> = self
154            .client
155            .request(WorkspaceSymbolRequest::METHOD, params)
156            .await
157            .with_context(|| format!("workspace symbol query failed for {query:?}"))?;
158
159        let symbols = response
160            .map(|response| normalize_symbols(response, self.symbol_names))
161            .unwrap_or_default();
162        Ok(deduplicate_symbols(symbols.into_iter().filter(|symbol| {
163            symbol_belongs_to_workspace(symbol, &self.workspace_root)
164        })))
165    }
166}
167
168#[derive(Clone)]
169pub struct HierarchyClient {
170    client: JsonRpcClient,
171    workspace_root: PathBuf,
172    symbol_names: SymbolNameAdapter,
173    document_symbols: DocumentSymbolCache,
174}
175
176type DocumentSymbolCache = Arc<Mutex<HashMap<Url, Arc<OnceCell<Vec<DocumentSymbolOwner>>>>>>;
177
178impl fmt::Debug for HierarchyClient {
179    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
180        formatter
181            .debug_struct("HierarchyClient")
182            .finish_non_exhaustive()
183    }
184}
185
186impl HierarchyClient {
187    pub async fn query(&self, mut query: HierarchyQuery) -> Result<HierarchyResponse> {
188        let (document_position, resolved_location) =
189            self.resolve_document_position(&query.symbol).await?;
190        query.symbol.location = Some(resolved_location);
191        let children = match query.symbol.kind {
192            HierarchyKind::Call => {
193                self.call_children(document_position, query.direction)
194                    .await?
195            }
196            HierarchyKind::Type => {
197                self.type_children(document_position, query.direction)
198                    .await?
199            }
200        };
201
202        Ok(HierarchyResponse {
203            query,
204            children: deduplicate_identities(children),
205            source: FetchSource::Lsp,
206        })
207    }
208
209    async fn resolve_document_position(
210        &self,
211        symbol: &SymbolIdentity,
212    ) -> Result<(TextDocumentPositionParams, SourceLocation)> {
213        if let Some(location) = symbol.location.as_ref()
214            && let (Some(line), Some(character)) = (location.line, location.character)
215        {
216            let uri = Url::parse(&location.uri)
217                .with_context(|| format!("invalid symbol URI: {}", location.uri))?;
218            return Ok(document_position(uri, Position::new(line, character)));
219        }
220
221        let lookup_name = symbol.symbol.rsplit("::").next().unwrap_or(&symbol.symbol);
222        let candidates = WorkspaceSymbolClient {
223            client: self.client.clone(),
224            workspace_root: self.workspace_root.clone(),
225            symbol_names: self.symbol_names,
226        }
227        .query(lookup_name)
228        .await?
229        .into_iter()
230        .filter(|candidate| {
231            candidate.range.is_some()
232                && candidate.name.rsplit("::").next() == Some(lookup_name)
233                && symbol_kind_matches_hierarchy(symbol.kind, candidate.kind)
234        })
235        .collect::<Vec<_>>();
236
237        let candidate = match candidates.as_slice() {
238            [candidate] => candidate,
239            [] => bail!(
240                "could not resolve {:?} to a workspace symbol with a source position",
241                symbol.symbol
242            ),
243            _ => bail!(
244                "symbol {:?} is ambiguous; add it through ac/at to select an exact location",
245                symbol.symbol
246            ),
247        };
248        let position = candidate
249            .range
250            .expect("workspace symbol candidates were filtered to exact locations")
251            .start;
252        Ok(document_position(candidate.uri.clone(), position))
253    }
254
255    async fn call_children(
256        &self,
257        document_position: TextDocumentPositionParams,
258        direction: HierarchyDirection,
259    ) -> Result<Vec<SymbolIdentity>> {
260        let prepared: Option<Vec<CallHierarchyItem>> = self
261            .client
262            .request(
263                CallHierarchyPrepare::METHOD,
264                CallHierarchyPrepareParams {
265                    text_document_position_params: document_position,
266                    work_done_progress_params: WorkDoneProgressParams::default(),
267                },
268            )
269            .await
270            .context("failed to prepare call hierarchy")?;
271        let Some(item) = prepared.and_then(|items| items.into_iter().next()) else {
272            return Ok(Vec::new());
273        };
274
275        match direction {
276            HierarchyDirection::Incoming => {
277                let calls: Option<Vec<CallHierarchyIncomingCall>> = self
278                    .client
279                    .request(
280                        CallHierarchyIncomingCalls::METHOD,
281                        CallHierarchyIncomingCallsParams {
282                            item,
283                            work_done_progress_params: WorkDoneProgressParams::default(),
284                            partial_result_params: PartialResultParams::default(),
285                        },
286                    )
287                    .await
288                    .context("failed to query incoming calls")?;
289                self.call_item_identities(
290                    calls
291                        .unwrap_or_default()
292                        .into_iter()
293                        .map(|call| call.from)
294                        .collect(),
295                )
296                .await
297            }
298            HierarchyDirection::Outgoing => {
299                let calls: Option<Vec<CallHierarchyOutgoingCall>> = self
300                    .client
301                    .request(
302                        CallHierarchyOutgoingCalls::METHOD,
303                        CallHierarchyOutgoingCallsParams {
304                            item,
305                            work_done_progress_params: WorkDoneProgressParams::default(),
306                            partial_result_params: PartialResultParams::default(),
307                        },
308                    )
309                    .await
310                    .context("failed to query outgoing calls")?;
311                self.call_item_identities(
312                    calls
313                        .unwrap_or_default()
314                        .into_iter()
315                        .map(|call| call.to)
316                        .collect(),
317                )
318                .await
319            }
320        }
321    }
322
323    async fn call_item_identities(
324        &self,
325        items: Vec<CallHierarchyItem>,
326    ) -> Result<Vec<SymbolIdentity>> {
327        let mut identities = Vec::with_capacity(items.len());
328        for item in items {
329            let container = if self.symbol_names.uses_document_symbols() {
330                self.document_symbol_container(&item).await
331            } else {
332                None
333            };
334            identities.push(call_item_identity(
335                item,
336                self.symbol_names,
337                container.as_deref(),
338            ));
339        }
340        Ok(identities)
341    }
342
343    async fn document_symbol_container(&self, item: &CallHierarchyItem) -> Option<String> {
344        if !matches!(
345            item.kind,
346            SymbolKind::FUNCTION | SymbolKind::METHOD | SymbolKind::CONSTRUCTOR
347        ) {
348            return None;
349        }
350
351        // rust-analyzer's call hierarchy exposes only a signature in `detail`.
352        // The map lock only creates a per-URI cell; the LSP round trip happens
353        // outside it, so different documents can resolve concurrently.
354        let document_symbols = {
355            let mut cache = self.document_symbols.lock().await;
356            Arc::clone(
357                cache
358                    .entry(item.uri.clone())
359                    .or_insert_with(|| Arc::new(OnceCell::new())),
360            )
361        };
362        let symbols = document_symbols
363            .get_or_init(|| async {
364                let response: Option<DocumentSymbolResponse> = self
365                    .client
366                    .request(
367                        DocumentSymbolRequest::METHOD,
368                        DocumentSymbolParams {
369                            text_document: TextDocumentIdentifier::new(item.uri.clone()),
370                            work_done_progress_params: WorkDoneProgressParams::default(),
371                            partial_result_params: PartialResultParams::default(),
372                        },
373                    )
374                    .await
375                    .ok()
376                    .flatten();
377                response.map(normalize_document_symbols).unwrap_or_default()
378            })
379            .await;
380        find_document_symbol_container(symbols, item).map(str::to_owned)
381    }
382
383    async fn type_children(
384        &self,
385        document_position: TextDocumentPositionParams,
386        direction: HierarchyDirection,
387    ) -> Result<Vec<SymbolIdentity>> {
388        let prepared: Option<Vec<TypeHierarchyItem>> = self
389            .client
390            .request(
391                TypeHierarchyPrepare::METHOD,
392                TypeHierarchyPrepareParams {
393                    text_document_position_params: document_position,
394                    work_done_progress_params: WorkDoneProgressParams::default(),
395                },
396            )
397            .await
398            .context("failed to prepare type hierarchy")?;
399        let Some(item) = prepared.and_then(|items| items.into_iter().next()) else {
400            return Ok(Vec::new());
401        };
402
403        let items: Option<Vec<TypeHierarchyItem>> = match direction {
404            HierarchyDirection::Incoming => self
405                .client
406                .request(
407                    TypeHierarchySupertypes::METHOD,
408                    TypeHierarchySupertypesParams {
409                        item,
410                        work_done_progress_params: WorkDoneProgressParams::default(),
411                        partial_result_params: PartialResultParams::default(),
412                    },
413                )
414                .await
415                .context("failed to query supertypes")?,
416            HierarchyDirection::Outgoing => self
417                .client
418                .request(
419                    TypeHierarchySubtypes::METHOD,
420                    TypeHierarchySubtypesParams {
421                        item,
422                        work_done_progress_params: WorkDoneProgressParams::default(),
423                        partial_result_params: PartialResultParams::default(),
424                    },
425                )
426                .await
427                .context("failed to query subtypes")?,
428        };
429        Ok(items
430            .unwrap_or_default()
431            .into_iter()
432            .map(type_item_identity)
433            .collect())
434    }
435}
436
437pub struct LspProvider {
438    child: Child,
439    client: JsonRpcClient,
440    connection_task: JoinHandle<Result<()>>,
441    workspace_root: PathBuf,
442    server_info: Option<ServerInfo>,
443    symbol_names: SymbolNameAdapter,
444    document_symbols: DocumentSymbolCache,
445    status_receiver: Option<mpsc::UnboundedReceiver<LspStatusUpdate>>,
446}
447
448impl fmt::Debug for LspProvider {
449    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
450        formatter
451            .debug_struct("LspProvider")
452            .field("workspace_root", &self.workspace_root)
453            .field("server_info", &self.server_info)
454            .finish_non_exhaustive()
455    }
456}
457
458impl LspProvider {
459    pub async fn start(config: LspConfig) -> Result<Self> {
460        let workspace_root = config.workspace_root.canonicalize().with_context(|| {
461            format!(
462                "failed to resolve workspace root {}",
463                config.workspace_root.display()
464            )
465        })?;
466        if !workspace_root.is_dir() {
467            bail!(
468                "workspace root is not a directory: {}",
469                workspace_root.display()
470            );
471        }
472
473        let workspace_uri = Url::from_directory_path(&workspace_root).map_err(|()| {
474            anyhow::anyhow!(
475                "workspace root cannot be represented as a file URI: {}",
476                workspace_root.display()
477            )
478        })?;
479        let workspace_name = workspace_name(&workspace_root);
480
481        let mut command = Command::new(&config.program);
482        command
483            .args(&config.args)
484            .current_dir(&workspace_root)
485            .stdin(Stdio::piped())
486            .stdout(Stdio::piped())
487            .stderr(Stdio::null())
488            .kill_on_drop(true);
489
490        let mut child = command.spawn().with_context(|| {
491            format!(
492                "failed to start language server {}",
493                config.program.to_string_lossy()
494            )
495        })?;
496        let stdin = child
497            .stdin
498            .take()
499            .context("language server did not expose stdin")?;
500        let stdout = child
501            .stdout
502            .take()
503            .context("language server did not expose stdout")?;
504        let (client, status_receiver, connection_task) = spawn_json_rpc(
505            BufReader::new(stdout),
506            stdin,
507            workspace_uri.clone(),
508            workspace_name.clone(),
509        );
510
511        let capabilities = client_capabilities();
512        let initialization_options = workspace_symbol_initialization_options(
513            &config.program,
514            config.initialization_options.clone(),
515        );
516        let initialize_params = InitializeParams {
517            process_id: Some(std::process::id()),
518            root_uri: Some(workspace_uri.clone()),
519            initialization_options,
520            capabilities,
521            workspace_folders: Some(vec![WorkspaceFolder {
522                uri: workspace_uri,
523                name: workspace_name,
524            }]),
525            client_info: Some(ClientInfo {
526                name: env!("CARGO_PKG_NAME").to_owned(),
527                version: Some(env!("CARGO_PKG_VERSION").to_owned()),
528            }),
529            ..InitializeParams::default()
530        };
531
532        let initialize_result: InitializeResult = client
533            .request(Initialize::METHOD, initialize_params)
534            .await
535            .context("language server initialization failed")?;
536        if !uses_utf16_positions(initialize_result.capabilities.position_encoding.as_ref()) {
537            bail!("language server selected a position encoding other than UTF-16");
538        }
539        if !workspace_symbol_supported(&initialize_result) {
540            bail!("language server does not support workspace/symbol");
541        }
542
543        client
544            .notify("initialized", json!({}))
545            .await
546            .context("failed to notify language server that initialization completed")?;
547
548        let symbol_names = SymbolNameAdapter::detect(
549            &config.program,
550            initialize_result
551                .server_info
552                .as_ref()
553                .map(|info| info.name.as_str()),
554        );
555        Ok(Self {
556            child,
557            client,
558            connection_task,
559            workspace_root,
560            server_info: initialize_result.server_info,
561            symbol_names,
562            document_symbols: Arc::new(Mutex::new(HashMap::new())),
563            status_receiver: Some(status_receiver),
564        })
565    }
566
567    pub fn workspace_root(&self) -> &Path {
568        &self.workspace_root
569    }
570
571    pub fn server_info(&self) -> Option<&ServerInfo> {
572        self.server_info.as_ref()
573    }
574
575    pub fn take_status_receiver(&mut self) -> Option<mpsc::UnboundedReceiver<LspStatusUpdate>> {
576        self.status_receiver.take()
577    }
578
579    pub fn workspace_symbol_client(&self) -> WorkspaceSymbolClient {
580        // The clone owns only an actor sender. Child process ownership stays in
581        // LspProvider, which keeps shutdown deterministic in main.
582        WorkspaceSymbolClient {
583            client: self.client.clone(),
584            workspace_root: self.workspace_root.clone(),
585            symbol_names: self.symbol_names,
586        }
587    }
588
589    pub fn hierarchy_client(&self) -> HierarchyClient {
590        HierarchyClient {
591            client: self.client.clone(),
592            workspace_root: self.workspace_root.clone(),
593            symbol_names: self.symbol_names,
594            document_symbols: Arc::clone(&self.document_symbols),
595        }
596    }
597
598    pub async fn workspace_symbols(&self, query: &str) -> Result<Vec<WorkspaceSymbolMatch>> {
599        self.workspace_symbol_client().query(query).await
600    }
601
602    pub async fn shutdown(mut self) -> Result<()> {
603        let shutdown_result = self
604            .client
605            .request::<_, ()>(Shutdown::METHOD, ())
606            .await
607            .context("language server shutdown request failed");
608        let _ = self.client.notify("exit", Value::Null).await;
609
610        if timeout(SHUTDOWN_TIMEOUT, self.child.wait()).await.is_err() {
611            self.child
612                .kill()
613                .await
614                .context("failed to stop language server after shutdown timeout")?;
615            self.child
616                .wait()
617                .await
618                .context("failed to reap language server process")?;
619        }
620
621        self.connection_task.abort();
622        let _ = self.connection_task.await;
623
624        shutdown_result
625    }
626}
627
628fn client_capabilities() -> ClientCapabilities {
629    ClientCapabilities {
630        text_document: Some(TextDocumentClientCapabilities {
631            call_hierarchy: Some(CallHierarchyClientCapabilities::default()),
632            document_symbol: Some(DocumentSymbolClientCapabilities::default()),
633            type_hierarchy: Some(TypeHierarchyClientCapabilities::default()),
634            ..TextDocumentClientCapabilities::default()
635        }),
636        workspace: Some(WorkspaceClientCapabilities {
637            symbol: Some(WorkspaceSymbolClientCapabilities::default()),
638            workspace_folders: Some(true),
639            configuration: Some(true),
640            ..WorkspaceClientCapabilities::default()
641        }),
642        window: Some(WindowClientCapabilities {
643            work_done_progress: Some(true),
644            ..WindowClientCapabilities::default()
645        }),
646        general: Some(GeneralClientCapabilities {
647            position_encodings: Some(vec![PositionEncodingKind::UTF16]),
648            ..GeneralClientCapabilities::default()
649        }),
650        experimental: Some(json!({
651            "serverStatusNotification": true,
652        })),
653    }
654}
655
656fn uses_utf16_positions(position_encoding: Option<&PositionEncodingKind>) -> bool {
657    position_encoding.is_none_or(|encoding| encoding == &PositionEncodingKind::UTF16)
658}
659
660#[derive(Clone)]
661struct JsonRpcClient {
662    commands: mpsc::Sender<JsonRpcCommand>,
663    cancellations: mpsc::UnboundedSender<u64>,
664}
665
666enum JsonRpcCommand {
667    Request {
668        method: String,
669        params: Value,
670        started: oneshot::Sender<u64>,
671        response: oneshot::Sender<std::result::Result<Value, String>>,
672    },
673    Notify {
674        method: String,
675        params: Value,
676        response: oneshot::Sender<std::result::Result<(), String>>,
677    },
678}
679
680struct RequestCancellationGuard {
681    request_id: u64,
682    cancellations: mpsc::UnboundedSender<u64>,
683    armed: bool,
684}
685
686impl RequestCancellationGuard {
687    fn disarm(&mut self) {
688        self.armed = false;
689    }
690}
691
692impl Drop for RequestCancellationGuard {
693    fn drop(&mut self) {
694        if self.armed {
695            // Drop cannot await actor I/O. The unbounded control channel makes
696            // cancellation reliable even when the bounded request queue is full.
697            let _ = self.cancellations.send(self.request_id);
698        }
699    }
700}
701
702impl JsonRpcClient {
703    async fn request<P, T>(&self, method: &str, params: P) -> Result<T>
704    where
705        P: Serialize,
706        T: DeserializeOwned,
707    {
708        let params = serde_json::to_value(params)
709            .with_context(|| format!("failed to encode parameters for LSP request {method}"))?;
710        let (started_sender, started_receiver) = oneshot::channel();
711        let (response_sender, response_receiver) = oneshot::channel();
712        self.commands
713            .send(JsonRpcCommand::Request {
714                method: method.to_owned(),
715                params,
716                started: started_sender,
717                response: response_sender,
718            })
719            .await
720            .map_err(|_| anyhow::anyhow!("LSP connection closed before request {method}"))?;
721        let request_id = started_receiver.await.map_err(|_| {
722            anyhow::anyhow!("LSP connection closed while starting request {method}")
723        })?;
724        let mut cancellation_guard = RequestCancellationGuard {
725            request_id,
726            cancellations: self.cancellations.clone(),
727            armed: true,
728        };
729        let response = response_receiver
730            .await
731            .map_err(|_| anyhow::anyhow!("LSP connection closed during request {method}"))?
732            .map_err(anyhow::Error::msg)?;
733        cancellation_guard.disarm();
734
735        serde_json::from_value(response)
736            .with_context(|| format!("invalid response to LSP request {method}"))
737    }
738
739    async fn notify<P>(&self, method: &str, params: P) -> Result<()>
740    where
741        P: Serialize,
742    {
743        let params = serde_json::to_value(params).with_context(|| {
744            format!("failed to encode parameters for LSP notification {method}")
745        })?;
746        let (response_sender, response_receiver) = oneshot::channel();
747        self.commands
748            .send(JsonRpcCommand::Notify {
749                method: method.to_owned(),
750                params,
751                response: response_sender,
752            })
753            .await
754            .map_err(|_| anyhow::anyhow!("LSP connection closed before notification {method}"))?;
755        response_receiver
756            .await
757            .map_err(|_| anyhow::anyhow!("LSP connection closed during notification {method}"))?
758            .map_err(anyhow::Error::msg)
759    }
760}
761
762fn spawn_json_rpc<R, W>(
763    reader: R,
764    writer: W,
765    workspace_uri: Url,
766    workspace_name: String,
767) -> (
768    JsonRpcClient,
769    mpsc::UnboundedReceiver<LspStatusUpdate>,
770    JoinHandle<Result<()>>,
771)
772where
773    R: AsyncBufRead + Send + Unpin + 'static,
774    W: AsyncWrite + Send + Unpin + 'static,
775{
776    // The reader must run even while no user request is active: servers commonly
777    // send workspace/configuration immediately after initialization and may wait
778    // for its response before indexing. The actor is the sole stdin writer so
779    // concurrent client requests and server-request replies cannot interleave.
780    let (command_sender, command_receiver) = mpsc::channel(32);
781    let (cancellation_sender, cancellation_receiver) = mpsc::unbounded_channel();
782    let (status_sender, status_receiver) = mpsc::unbounded_channel();
783    let (incoming_sender, incoming_receiver) = mpsc::channel(64);
784    let reader_task = tokio::spawn(read_messages(reader, incoming_sender));
785    let connection_task = tokio::spawn(async move {
786        let result = run_json_rpc(
787            writer,
788            command_receiver,
789            cancellation_receiver,
790            incoming_receiver,
791            status_sender,
792            workspace_uri,
793            workspace_name,
794        )
795        .await;
796        reader_task.abort();
797        let _ = reader_task.await;
798        result
799    });
800
801    let client = JsonRpcClient {
802        commands: command_sender,
803        cancellations: cancellation_sender,
804    };
805    (client, status_receiver, connection_task)
806}
807
808async fn read_messages<R>(mut reader: R, sender: mpsc::Sender<std::result::Result<Value, String>>)
809where
810    R: AsyncBufRead + Unpin,
811{
812    loop {
813        match read_message(&mut reader).await {
814            Ok(message) => {
815                if sender.send(Ok(message)).await.is_err() {
816                    break;
817                }
818            }
819            Err(error) => {
820                let _ = sender.send(Err(error.to_string())).await;
821                break;
822            }
823        }
824    }
825}
826
827async fn run_json_rpc<W>(
828    mut writer: W,
829    mut commands: mpsc::Receiver<JsonRpcCommand>,
830    mut cancellations: mpsc::UnboundedReceiver<u64>,
831    mut incoming: mpsc::Receiver<std::result::Result<Value, String>>,
832    status_sender: mpsc::UnboundedSender<LspStatusUpdate>,
833    workspace_uri: Url,
834    workspace_name: String,
835) -> Result<()>
836where
837    W: AsyncWrite + Unpin,
838{
839    let mut next_request_id = 1_u64;
840    let mut pending = HashMap::new();
841    let mut progress_tracker = LspProgressTracker::default();
842
843    let connection_result = loop {
844        tokio::select! {
845            command = commands.recv() => {
846                let Some(command) = command else {
847                    break Ok(());
848                };
849                match command {
850                    JsonRpcCommand::Request { method, params, started, response } => {
851                        let request_id = next_request_id;
852                        next_request_id += 1;
853                        let message = json!({
854                            "jsonrpc": "2.0",
855                            "id": request_id,
856                            "method": method,
857                            "params": params,
858                        });
859                        if let Err(error) = write_message(&mut writer, &message).await {
860                            let _ = response.send(Err(error.to_string()));
861                            break Err(error);
862                        }
863                        pending.insert(request_id, response);
864                        if started.send(request_id).is_err() {
865                            pending.remove(&request_id);
866                            write_cancel_request(&mut writer, request_id).await?;
867                        }
868                    }
869                    JsonRpcCommand::Notify { method, params, response } => {
870                        let message = json!({
871                            "jsonrpc": "2.0",
872                            "method": method,
873                            "params": params,
874                        });
875                        match write_message(&mut writer, &message).await {
876                            Ok(()) => {
877                                let _ = response.send(Ok(()));
878                            }
879                            Err(error) => {
880                                let _ = response.send(Err(error.to_string()));
881                                break Err(error);
882                            }
883                        }
884                    }
885                }
886            }
887            request_id = cancellations.recv() => {
888                let Some(request_id) = request_id else {
889                    break Ok(());
890                };
891                if pending.remove(&request_id).is_some() {
892                    write_cancel_request(&mut writer, request_id).await?;
893                }
894            }
895            message = incoming.recv() => {
896                let Some(message) = message else {
897                    break Err(anyhow::anyhow!("LSP message reader stopped unexpectedly"));
898                };
899                let message = match message {
900                    Ok(message) => message,
901                    Err(error) => break Err(anyhow::Error::msg(error)),
902                };
903
904                if let Some(request_id) = response_id(&message) {
905                    if let Some(response) = pending.remove(&request_id) {
906                        let result = match message.get("error") {
907                            Some(error) if !error.is_null() => Err(format!(
908                                "LSP request failed: {error}"
909                            )),
910                            _ => Ok(message.get("result").cloned().unwrap_or(Value::Null)),
911                        };
912                        let _ = response.send(result);
913                    }
914                } else if message.get("method").is_some() {
915                    handle_server_notification(
916                        &message,
917                        &mut progress_tracker,
918                        &status_sender,
919                    );
920                    if let Err(error) = handle_server_message(
921                            &mut writer,
922                            &message,
923                            &workspace_uri,
924                            &workspace_name,
925                        )
926                        .await
927                    {
928                        break Err(error);
929                    }
930                }
931            }
932        }
933    };
934
935    let failure = connection_result
936        .as_ref()
937        .err()
938        .map_or_else(|| "LSP connection closed".to_owned(), ToString::to_string);
939    let _ = status_sender.send(LspStatusUpdate::Disconnected(failure.clone()));
940    for (_, response) in pending {
941        let _ = response.send(Err(failure.clone()));
942    }
943
944    connection_result
945}
946
947async fn write_cancel_request<W>(writer: &mut W, request_id: u64) -> Result<()>
948where
949    W: AsyncWrite + Unpin,
950{
951    write_message(
952        writer,
953        &json!({
954            "jsonrpc": "2.0",
955            "method": "$/cancelRequest",
956            "params": { "id": request_id },
957        }),
958    )
959    .await
960}
961
962fn handle_server_notification(
963    message: &Value,
964    tracker: &mut LspProgressTracker,
965    sender: &mpsc::UnboundedSender<LspStatusUpdate>,
966) {
967    match message.get("method").and_then(Value::as_str) {
968        Some("$/progress") => {
969            let Some(params) = message.get("params").cloned() else {
970                return;
971            };
972            let Ok(params) = serde_json::from_value::<ProgressParams>(params) else {
973                return;
974            };
975            let ProgressParamsValue::WorkDone(progress) = params.value;
976            tracker.update(params.token, progress, sender);
977        }
978        Some("experimental/serverStatus") => {
979            let Some(params) = message.get("params") else {
980                return;
981            };
982            let health = params.get("health").and_then(Value::as_str).unwrap_or("ok");
983            let quiescent = params
984                .get("quiescent")
985                .and_then(Value::as_bool)
986                .unwrap_or(false);
987            let message = params
988                .get("message")
989                .and_then(Value::as_str)
990                .map(str::to_owned);
991
992            let update = match health {
993                "warning" => LspStatusUpdate::Warning(
994                    message.unwrap_or_else(|| "Language server reported a warning".to_owned()),
995                ),
996                "error" => LspStatusUpdate::Error(
997                    message.unwrap_or_else(|| "Language server reported an error".to_owned()),
998                ),
999                _ if quiescent => {
1000                    if tracker.emit_latest(sender) {
1001                        return;
1002                    }
1003                    LspStatusUpdate::Ready { message }
1004                }
1005                _ => {
1006                    if tracker.emit_latest(sender) {
1007                        return;
1008                    }
1009                    LspStatusUpdate::Progress {
1010                        title: "rust-analyzer".to_owned(),
1011                        message: message.or_else(|| Some("Background work in progress".to_owned())),
1012                        percentage: None,
1013                    }
1014                }
1015            };
1016            let _ = sender.send(update);
1017        }
1018        _ => {}
1019    }
1020}
1021
1022impl LspProgressTracker {
1023    fn update(
1024        &mut self,
1025        token: NumberOrString,
1026        progress: WorkDoneProgress,
1027        sender: &mpsc::UnboundedSender<LspStatusUpdate>,
1028    ) {
1029        let token = progress_token_key(token);
1030        self.next_sequence = self.next_sequence.wrapping_add(1);
1031        match progress {
1032            WorkDoneProgress::Begin(progress) => {
1033                self.active.insert(
1034                    token,
1035                    ActiveProgress {
1036                        sequence: self.next_sequence,
1037                        title: progress.title,
1038                        message: progress.message,
1039                        percentage: progress.percentage,
1040                    },
1041                );
1042                self.emit_latest(sender);
1043            }
1044            WorkDoneProgress::Report(progress) => {
1045                if let Some(active) = self.active.get_mut(&token) {
1046                    active.sequence = self.next_sequence;
1047                    if progress.message.is_some() {
1048                        active.message = progress.message;
1049                    }
1050                    if progress.percentage.is_some() {
1051                        active.percentage = progress.percentage;
1052                    }
1053                    self.emit_latest(sender);
1054                }
1055            }
1056            WorkDoneProgress::End(progress) => {
1057                self.active.remove(&token);
1058                if !self.emit_latest(sender) {
1059                    let _ = sender.send(LspStatusUpdate::Ready {
1060                        message: progress.message,
1061                    });
1062                }
1063            }
1064        }
1065    }
1066
1067    fn emit_latest(&self, sender: &mpsc::UnboundedSender<LspStatusUpdate>) -> bool {
1068        let Some(progress) = self
1069            .active
1070            .values()
1071            .max_by_key(|progress| progress.sequence)
1072        else {
1073            return false;
1074        };
1075        let _ = sender.send(LspStatusUpdate::Progress {
1076            title: progress.title.clone(),
1077            message: progress.message.clone(),
1078            percentage: progress.percentage,
1079        });
1080        true
1081    }
1082}
1083
1084fn progress_token_key(token: NumberOrString) -> String {
1085    match token {
1086        NumberOrString::Number(number) => format!("number:{number}"),
1087        NumberOrString::String(string) => format!("string:{string}"),
1088    }
1089}
1090
1091async fn handle_server_message<W>(
1092    writer: &mut W,
1093    message: &Value,
1094    workspace_uri: &Url,
1095    workspace_name: &str,
1096) -> Result<()>
1097where
1098    W: AsyncWrite + Unpin,
1099{
1100    let Some(id) = message.get("id").cloned() else {
1101        return Ok(());
1102    };
1103    let method = message
1104        .get("method")
1105        .and_then(Value::as_str)
1106        .context("LSP server request has no method")?;
1107
1108    let response = match method {
1109        "workspace/configuration" => {
1110            let values = message
1111                .pointer("/params/items")
1112                .and_then(Value::as_array)
1113                .map(|items| {
1114                    items
1115                        .iter()
1116                        .map(|item| {
1117                            requested_configuration(item.get("section").and_then(Value::as_str))
1118                        })
1119                        .collect::<Vec<_>>()
1120                })
1121                .unwrap_or_default();
1122            json!({
1123                "jsonrpc": "2.0",
1124                "id": id,
1125                "result": values,
1126            })
1127        }
1128        "workspace/workspaceFolders" => json!({
1129            "jsonrpc": "2.0",
1130            "id": id,
1131            "result": [{
1132                "uri": workspace_uri,
1133                "name": workspace_name,
1134            }],
1135        }),
1136        "client/registerCapability"
1137        | "client/unregisterCapability"
1138        | "window/workDoneProgress/create"
1139        | "window/showMessageRequest" => json!({
1140            "jsonrpc": "2.0",
1141            "id": id,
1142            "result": null,
1143        }),
1144        _ => json!({
1145            "jsonrpc": "2.0",
1146            "id": id,
1147            "error": {
1148                "code": -32601,
1149                "message": format!("cgraph does not implement {method}"),
1150            },
1151        }),
1152    };
1153
1154    write_message(writer, &response).await
1155}
1156
1157fn requested_configuration(section: Option<&str>) -> Value {
1158    match section {
1159        Some("rust-analyzer") => json!({
1160            "workspace": {
1161                "symbol": {
1162                    "search": {
1163                        "kind": "all_symbols",
1164                        "scope": "workspace",
1165                    }
1166                }
1167            }
1168        }),
1169        Some("rust-analyzer.workspace.symbol.search.kind") => json!("all_symbols"),
1170        Some("rust-analyzer.workspace.symbol.search.scope") => json!("workspace"),
1171        _ => Value::Null,
1172    }
1173}
1174
1175fn workspace_name(workspace_root: &Path) -> String {
1176    workspace_root
1177        .file_name()
1178        .and_then(OsStr::to_str)
1179        .unwrap_or("workspace")
1180        .to_owned()
1181}
1182
1183fn workspace_symbol_supported(initialize_result: &InitializeResult) -> bool {
1184    match &initialize_result.capabilities.workspace_symbol_provider {
1185        Some(OneOf::Left(supported)) => *supported,
1186        Some(OneOf::Right(_)) => true,
1187        None => false,
1188    }
1189}
1190
1191fn workspace_symbol_initialization_options(
1192    program: &OsStr,
1193    options: Option<Value>,
1194) -> Option<Value> {
1195    if !is_rust_analyzer_program(program) {
1196        return options;
1197    }
1198
1199    let mut options = options.unwrap_or_else(|| json!({}));
1200    merge_json(
1201        &mut options,
1202        json!({
1203            "workspace": {
1204                "symbol": {
1205                    "search": {
1206                        "kind": "all_symbols",
1207                        "scope": "workspace",
1208                    }
1209                }
1210            }
1211        }),
1212    );
1213    Some(options)
1214}
1215
1216fn is_rust_analyzer_program(program: &OsStr) -> bool {
1217    let program_name = Path::new(program)
1218        .file_name()
1219        .and_then(OsStr::to_str)
1220        .unwrap_or_default();
1221    program_name.eq_ignore_ascii_case("rust-analyzer")
1222        || program_name.eq_ignore_ascii_case("rust-analyzer.exe")
1223}
1224
1225fn merge_json(target: &mut Value, overlay: Value) {
1226    match (target, overlay) {
1227        (Value::Object(target), Value::Object(overlay)) => {
1228            for (key, value) in overlay {
1229                merge_json(target.entry(key).or_insert(Value::Null), value);
1230            }
1231        }
1232        (target, overlay) => *target = overlay,
1233    }
1234}
1235
1236fn symbol_belongs_to_workspace(symbol: &WorkspaceSymbolMatch, workspace_root: &Path) -> bool {
1237    symbol
1238        .uri
1239        .to_file_path()
1240        .is_ok_and(|path| path.starts_with(workspace_root))
1241}
1242
1243fn deduplicate_symbols(
1244    symbols: impl IntoIterator<Item = WorkspaceSymbolMatch>,
1245) -> Vec<WorkspaceSymbolMatch> {
1246    let mut unique = Vec::new();
1247    for symbol in symbols {
1248        let duplicate = unique.iter().any(|existing: &WorkspaceSymbolMatch| {
1249            existing.name == symbol.name
1250                && existing.kind == symbol.kind
1251                && existing.uri == symbol.uri
1252                && existing.range == symbol.range
1253                && existing.container_name == symbol.container_name
1254        });
1255        if !duplicate {
1256            unique.push(symbol);
1257        }
1258    }
1259    unique
1260}
1261
1262fn document_position(uri: Url, position: Position) -> (TextDocumentPositionParams, SourceLocation) {
1263    let location = SourceLocation {
1264        uri: uri.to_string(),
1265        line: Some(position.line),
1266        character: Some(position.character),
1267    };
1268    (
1269        TextDocumentPositionParams::new(TextDocumentIdentifier::new(uri), position),
1270        location,
1271    )
1272}
1273
1274fn symbol_kind_matches_hierarchy(kind: HierarchyKind, symbol_kind: SymbolKind) -> bool {
1275    match kind {
1276        HierarchyKind::Call => matches!(
1277            symbol_kind,
1278            SymbolKind::FUNCTION | SymbolKind::METHOD | SymbolKind::CONSTRUCTOR
1279        ),
1280        HierarchyKind::Type => matches!(
1281            symbol_kind,
1282            SymbolKind::CLASS
1283                | SymbolKind::INTERFACE
1284                | SymbolKind::STRUCT
1285                | SymbolKind::ENUM
1286                | SymbolKind::TYPE_PARAMETER
1287        ),
1288    }
1289}
1290
1291fn call_item_identity(
1292    item: CallHierarchyItem,
1293    symbol_names: SymbolNameAdapter,
1294    document_container: Option<&str>,
1295) -> SymbolIdentity {
1296    let symbol = symbol_names.call_hierarchy_item(
1297        &item.name,
1298        item.kind,
1299        item.detail.as_deref(),
1300        document_container,
1301    );
1302    SymbolIdentity {
1303        symbol,
1304        kind: HierarchyKind::Call,
1305        location: Some(SourceLocation {
1306            uri: item.uri.to_string(),
1307            line: Some(item.selection_range.start.line),
1308            character: Some(item.selection_range.start.character),
1309        }),
1310    }
1311}
1312
1313#[derive(Clone, Debug)]
1314struct DocumentSymbolOwner {
1315    name: String,
1316    kind: SymbolKind,
1317    range: Range,
1318    container_name: Option<String>,
1319}
1320
1321fn normalize_document_symbols(response: DocumentSymbolResponse) -> Vec<DocumentSymbolOwner> {
1322    match response {
1323        DocumentSymbolResponse::Flat(symbols) => {
1324            symbols.into_iter().map(document_symbol_owner).collect()
1325        }
1326        DocumentSymbolResponse::Nested(symbols) => {
1327            let mut normalized = Vec::new();
1328            normalize_nested_document_symbols(&symbols, None, &mut normalized);
1329            normalized
1330        }
1331    }
1332}
1333
1334#[allow(deprecated)]
1335fn document_symbol_owner(symbol: SymbolInformation) -> DocumentSymbolOwner {
1336    DocumentSymbolOwner {
1337        name: symbol.name,
1338        kind: symbol.kind,
1339        range: symbol.location.range,
1340        container_name: symbol.container_name,
1341    }
1342}
1343
1344fn normalize_nested_document_symbols(
1345    symbols: &[DocumentSymbol],
1346    container_name: Option<&str>,
1347    normalized: &mut Vec<DocumentSymbolOwner>,
1348) {
1349    for symbol in symbols {
1350        normalized.push(DocumentSymbolOwner {
1351            name: symbol.name.clone(),
1352            kind: symbol.kind,
1353            range: symbol.range,
1354            container_name: container_name.map(str::to_owned),
1355        });
1356        if let Some(children) = symbol.children.as_deref() {
1357            normalize_nested_document_symbols(children, Some(&symbol.name), normalized);
1358        }
1359    }
1360}
1361
1362fn find_document_symbol_container<'a>(
1363    symbols: &'a [DocumentSymbolOwner],
1364    item: &CallHierarchyItem,
1365) -> Option<&'a str> {
1366    symbols
1367        .iter()
1368        .filter(|symbol| {
1369            symbol.name == item.name
1370                && matches!(
1371                    symbol.kind,
1372                    SymbolKind::FUNCTION | SymbolKind::METHOD | SymbolKind::CONSTRUCTOR
1373                )
1374                && range_contains_position(symbol.range, item.selection_range.start)
1375        })
1376        .min_by_key(|symbol| range_span_key(symbol.range))
1377        .and_then(|symbol| symbol.container_name.as_deref())
1378}
1379
1380fn range_contains_position(range: Range, position: Position) -> bool {
1381    position_after_or_equal(position, range.start) && position_after_or_equal(range.end, position)
1382}
1383
1384fn position_after_or_equal(left: Position, right: Position) -> bool {
1385    (left.line, left.character) >= (right.line, right.character)
1386}
1387
1388fn range_span_key(range: Range) -> (u32, u32) {
1389    (
1390        range.end.line.saturating_sub(range.start.line),
1391        range.end.character.saturating_sub(range.start.character),
1392    )
1393}
1394
1395fn type_item_identity(item: TypeHierarchyItem) -> SymbolIdentity {
1396    SymbolIdentity {
1397        symbol: item.name,
1398        kind: HierarchyKind::Type,
1399        location: Some(SourceLocation {
1400            uri: item.uri.to_string(),
1401            line: Some(item.selection_range.start.line),
1402            character: Some(item.selection_range.start.character),
1403        }),
1404    }
1405}
1406
1407fn deduplicate_identities(
1408    identities: impl IntoIterator<Item = SymbolIdentity>,
1409) -> Vec<SymbolIdentity> {
1410    let mut unique = Vec::new();
1411    for identity in identities {
1412        if !unique.contains(&identity) {
1413            unique.push(identity);
1414        }
1415    }
1416    unique
1417}
1418
1419fn normalize_symbols(
1420    response: WorkspaceSymbolResponse,
1421    symbol_names: SymbolNameAdapter,
1422) -> Vec<WorkspaceSymbolMatch> {
1423    match response {
1424        WorkspaceSymbolResponse::Flat(symbols) => symbols
1425            .into_iter()
1426            .map(|symbol| {
1427                let name = symbol_names.workspace_symbol(
1428                    &symbol.name,
1429                    symbol.kind,
1430                    symbol.container_name.as_deref(),
1431                );
1432                WorkspaceSymbolMatch {
1433                    name,
1434                    kind: symbol.kind,
1435                    container_name: symbol.container_name,
1436                    uri: symbol.location.uri,
1437                    range: Some(symbol.location.range),
1438                }
1439            })
1440            .collect(),
1441        WorkspaceSymbolResponse::Nested(symbols) => symbols
1442            .into_iter()
1443            .map(|symbol| {
1444                let (uri, range) = match symbol.location {
1445                    OneOf::Left(Location { uri, range }) => (uri, Some(range)),
1446                    OneOf::Right(location) => (location.uri, None),
1447                };
1448                let name = symbol_names.workspace_symbol(
1449                    &symbol.name,
1450                    symbol.kind,
1451                    symbol.container_name.as_deref(),
1452                );
1453                WorkspaceSymbolMatch {
1454                    name,
1455                    kind: symbol.kind,
1456                    container_name: symbol.container_name,
1457                    uri,
1458                    range,
1459                }
1460            })
1461            .collect(),
1462    }
1463}
1464
1465fn response_id(message: &Value) -> Option<u64> {
1466    message.get("id").and_then(Value::as_u64)
1467}
1468
1469async fn read_message<R>(reader: &mut R) -> Result<Value>
1470where
1471    R: AsyncBufRead + Unpin,
1472{
1473    let mut content_length = None;
1474
1475    loop {
1476        let mut header = String::new();
1477        if reader.read_line(&mut header).await? == 0 {
1478            bail!("language server closed its output stream");
1479        }
1480        let header = header.trim_end_matches(['\r', '\n']);
1481        if header.is_empty() {
1482            break;
1483        }
1484
1485        let Some((name, value)) = header.split_once(':') else {
1486            bail!("malformed LSP header: {header:?}");
1487        };
1488        if name.eq_ignore_ascii_case("Content-Length") {
1489            content_length = Some(
1490                value
1491                    .trim()
1492                    .parse::<usize>()
1493                    .context("invalid LSP Content-Length header")?,
1494            );
1495        }
1496    }
1497
1498    let content_length = content_length.context("LSP message has no Content-Length header")?;
1499    if content_length > MAX_MESSAGE_SIZE {
1500        bail!("LSP message is too large: {content_length} bytes (limit: {MAX_MESSAGE_SIZE} bytes)");
1501    }
1502
1503    let mut body = vec![0; content_length];
1504    reader
1505        .read_exact(&mut body)
1506        .await
1507        .context("language server closed its output stream mid-message")?;
1508    serde_json::from_slice(&body).context("language server sent invalid JSON")
1509}
1510
1511async fn write_message<W>(writer: &mut W, message: &Value) -> Result<()>
1512where
1513    W: AsyncWrite + Unpin,
1514{
1515    let body = serde_json::to_vec(message).context("failed to encode LSP message")?;
1516    let header = format!("Content-Length: {}\r\n\r\n", body.len());
1517    writer.write_all(header.as_bytes()).await?;
1518    writer.write_all(&body).await?;
1519    writer.flush().await?;
1520    Ok(())
1521}
1522
1523#[cfg(test)]
1524mod tests {
1525    use std::{
1526        ffi::OsStr,
1527        path::{Path, PathBuf},
1528        time::Duration,
1529    };
1530
1531    use serde_json::{Value, json};
1532    use tokio::io::{BufReader, duplex, split};
1533    use tokio::time::timeout;
1534    use tower_lsp::lsp_types::{
1535        SymbolKind, Url, WorkspaceSymbolParams, WorkspaceSymbolResponse,
1536        request::{DocumentSymbolRequest, Request},
1537    };
1538
1539    use super::symbol_names::SymbolNameAdapter;
1540    use super::{
1541        HierarchyClient, LspProgressTracker, LspStatusUpdate, WorkspaceSymbolMatch,
1542        client_capabilities, deduplicate_symbols, handle_server_notification, normalize_symbols,
1543        read_message, requested_configuration, response_id, spawn_json_rpc,
1544        symbol_belongs_to_workspace, uses_utf16_positions, workspace_symbol_initialization_options,
1545        write_message,
1546    };
1547    use crate::{
1548        fetch::{FetchSource, HierarchyQuery},
1549        state::{HierarchyDirection, HierarchyKind, SourceLocation, SymbolIdentity},
1550    };
1551
1552    #[test]
1553    fn excludes_symbols_outside_the_workspace() {
1554        let project_symbol = symbol("file:///workspace/src/main.rs");
1555        let dependency_symbol = symbol("file:///registry/dependency/src/lib.rs");
1556
1557        assert!(symbol_belongs_to_workspace(
1558            &project_symbol,
1559            Path::new("/workspace")
1560        ));
1561        assert!(!symbol_belongs_to_workspace(
1562            &dependency_symbol,
1563            Path::new("/workspace")
1564        ));
1565    }
1566
1567    #[test]
1568    fn configures_rust_analyzer_for_project_only_all_symbol_queries() {
1569        assert_eq!(
1570            requested_configuration(Some("rust-analyzer.workspace.symbol.search.kind")),
1571            json!("all_symbols")
1572        );
1573        assert_eq!(
1574            requested_configuration(Some("rust-analyzer.workspace.symbol.search.scope")),
1575            json!("workspace")
1576        );
1577        assert_eq!(
1578            requested_configuration(Some("rust-analyzer.workspace.symbol.search.limit")),
1579            Value::Null
1580        );
1581        assert_eq!(requested_configuration(Some("clangd")), Value::Null);
1582
1583        let options = workspace_symbol_initialization_options(
1584            OsStr::new("rust-analyzer"),
1585            Some(json!({ "cargo": { "features": "all" } })),
1586        )
1587        .unwrap();
1588        assert_eq!(options["cargo"]["features"], "all");
1589        assert_eq!(
1590            options["workspace"]["symbol"]["search"]["kind"],
1591            "all_symbols"
1592        );
1593        assert_eq!(
1594            options["workspace"]["symbol"]["search"]["scope"],
1595            "workspace"
1596        );
1597        assert!(
1598            options["workspace"]["symbol"]["search"]
1599                .get("limit")
1600                .is_none()
1601        );
1602
1603        assert_eq!(
1604            workspace_symbol_initialization_options(
1605                OsStr::new("clangd"),
1606                Some(json!({ "clangd": true })),
1607            ),
1608            Some(json!({ "clangd": true }))
1609        );
1610    }
1611
1612    #[test]
1613    fn negotiates_only_utf16_source_positions() {
1614        let capabilities = serde_json::to_value(client_capabilities()).unwrap();
1615        assert_eq!(
1616            capabilities["general"]["positionEncodings"],
1617            json!(["utf-16"])
1618        );
1619        assert!(uses_utf16_positions(None));
1620        assert!(uses_utf16_positions(Some(
1621            &tower_lsp::lsp_types::PositionEncodingKind::UTF16
1622        )));
1623        assert!(!uses_utf16_positions(Some(
1624            &tower_lsp::lsp_types::PositionEncodingKind::UTF8
1625        )));
1626    }
1627
1628    #[test]
1629    fn deduplicates_identical_workspace_symbols() {
1630        let duplicate = symbol("file:///workspace/src/main.rs");
1631        assert_eq!(
1632            deduplicate_symbols([duplicate.clone(), duplicate.clone(), duplicate]),
1633            vec![symbol("file:///workspace/src/main.rs")]
1634        );
1635    }
1636
1637    #[tokio::test]
1638    async fn prepares_and_queries_outgoing_call_hierarchy() {
1639        let (client_stream, server_stream) = duplex(8 * 1024);
1640        let (client_reader, client_writer) = split(client_stream);
1641        let (server_reader, mut server_writer) = split(server_stream);
1642        let workspace_uri = Url::parse("file:///workspace").unwrap();
1643        let (rpc_client, _status_receiver, connection_task) = spawn_json_rpc(
1644            BufReader::new(client_reader),
1645            client_writer,
1646            workspace_uri,
1647            "workspace".to_owned(),
1648        );
1649        let hierarchy_client = HierarchyClient {
1650            client: rpc_client.clone(),
1651            workspace_root: PathBuf::from("/workspace"),
1652            symbol_names: SymbolNameAdapter::RustAnalyzer,
1653            document_symbols: Default::default(),
1654        };
1655        let query = HierarchyQuery {
1656            symbol: SymbolIdentity {
1657                symbol: "root".to_owned(),
1658                kind: HierarchyKind::Call,
1659                location: Some(SourceLocation {
1660                    uri: "file:///workspace/src/main.rs".to_owned(),
1661                    line: Some(4),
1662                    character: Some(3),
1663                }),
1664            },
1665            direction: HierarchyDirection::Outgoing,
1666        };
1667        let client_task = tokio::spawn(async move { hierarchy_client.query(query).await.unwrap() });
1668        let mut server_reader = BufReader::new(server_reader);
1669
1670        let prepare = read_message(&mut server_reader).await.unwrap();
1671        assert_eq!(prepare["method"], "textDocument/prepareCallHierarchy");
1672        assert_eq!(
1673            prepare["params"]["position"],
1674            json!({ "line": 4, "character": 3 })
1675        );
1676        write_message(
1677            &mut server_writer,
1678            &json!({
1679                "jsonrpc": "2.0",
1680                "id": response_id(&prepare).unwrap(),
1681                "result": [call_item("root", 4)]
1682            }),
1683        )
1684        .await
1685        .unwrap();
1686
1687        let outgoing = read_message(&mut server_reader).await.unwrap();
1688        assert_eq!(outgoing["method"], "callHierarchy/outgoingCalls");
1689        assert_eq!(outgoing["params"]["item"]["name"], "root");
1690        write_message(
1691            &mut server_writer,
1692            &json!({
1693                "jsonrpc": "2.0",
1694                "id": response_id(&outgoing).unwrap(),
1695                "result": [
1696                    { "to": rust_method_item("child", 8), "fromRanges": [] },
1697                    { "to": rust_method_item("child", 8), "fromRanges": [] }
1698                ]
1699            }),
1700        )
1701        .await
1702        .unwrap();
1703
1704        let document_symbols = read_message(&mut server_reader).await.unwrap();
1705        assert_eq!(document_symbols["method"], DocumentSymbolRequest::METHOD);
1706        assert_eq!(
1707            document_symbols["params"]["textDocument"]["uri"],
1708            "file:///workspace/src/main.rs"
1709        );
1710        write_message(
1711            &mut server_writer,
1712            &json!({
1713                "jsonrpc": "2.0",
1714                "id": response_id(&document_symbols).unwrap(),
1715                "result": [{
1716                    "name": "child",
1717                    "kind": 12,
1718                    "location": {
1719                        "uri": "file:///workspace/src/main.rs",
1720                        "range": {
1721                            "start": { "line": 8, "character": 0 },
1722                            "end": { "line": 10, "character": 1 }
1723                        }
1724                    },
1725                    "containerName": "impl Worker"
1726                }]
1727            }),
1728        )
1729        .await
1730        .unwrap();
1731
1732        let response = client_task.await.unwrap();
1733        assert_eq!(response.source, FetchSource::Lsp);
1734        assert_eq!(response.children.len(), 1);
1735        assert_eq!(response.children[0].symbol, "Worker::child");
1736        assert_eq!(response.children[0].kind, HierarchyKind::Call);
1737        assert_eq!(
1738            response.children[0].location.as_ref().unwrap().line,
1739            Some(8)
1740        );
1741
1742        drop(rpc_client);
1743        connection_task.abort();
1744        let _ = connection_task.await;
1745    }
1746
1747    #[tokio::test]
1748    async fn prepares_and_queries_type_supertypes() {
1749        let (client_stream, server_stream) = duplex(8 * 1024);
1750        let (client_reader, client_writer) = split(client_stream);
1751        let (server_reader, mut server_writer) = split(server_stream);
1752        let workspace_uri = Url::parse("file:///workspace").unwrap();
1753        let (rpc_client, _status_receiver, connection_task) = spawn_json_rpc(
1754            BufReader::new(client_reader),
1755            client_writer,
1756            workspace_uri,
1757            "workspace".to_owned(),
1758        );
1759        let hierarchy_client = HierarchyClient {
1760            client: rpc_client.clone(),
1761            workspace_root: PathBuf::from("/workspace"),
1762            symbol_names: SymbolNameAdapter::Standard,
1763            document_symbols: Default::default(),
1764        };
1765        let query = HierarchyQuery {
1766            symbol: SymbolIdentity {
1767                symbol: "Child".to_owned(),
1768                kind: HierarchyKind::Type,
1769                location: Some(SourceLocation {
1770                    uri: "file:///workspace/src/main.rs".to_owned(),
1771                    line: Some(10),
1772                    character: Some(7),
1773                }),
1774            },
1775            direction: HierarchyDirection::Incoming,
1776        };
1777        let client_task = tokio::spawn(async move { hierarchy_client.query(query).await.unwrap() });
1778        let mut server_reader = BufReader::new(server_reader);
1779
1780        let prepare = read_message(&mut server_reader).await.unwrap();
1781        assert_eq!(prepare["method"], "textDocument/prepareTypeHierarchy");
1782        write_message(
1783            &mut server_writer,
1784            &json!({
1785                "jsonrpc": "2.0",
1786                "id": response_id(&prepare).unwrap(),
1787                "result": [type_item("Child", 10)]
1788            }),
1789        )
1790        .await
1791        .unwrap();
1792
1793        let supertypes = read_message(&mut server_reader).await.unwrap();
1794        assert_eq!(supertypes["method"], "typeHierarchy/supertypes");
1795        write_message(
1796            &mut server_writer,
1797            &json!({
1798                "jsonrpc": "2.0",
1799                "id": response_id(&supertypes).unwrap(),
1800                "result": [type_item("Parent", 2)]
1801            }),
1802        )
1803        .await
1804        .unwrap();
1805
1806        let response = client_task.await.unwrap();
1807        assert_eq!(response.children.len(), 1);
1808        assert_eq!(response.children[0].symbol, "Parent");
1809        assert_eq!(response.children[0].kind, HierarchyKind::Type);
1810
1811        drop(rpc_client);
1812        connection_task.abort();
1813        let _ = connection_task.await;
1814    }
1815
1816    #[test]
1817    fn tracks_work_done_progress_until_the_last_operation_ends() {
1818        let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
1819        let mut tracker = LspProgressTracker::default();
1820
1821        handle_server_notification(
1822            &json!({
1823                "method": "$/progress",
1824                "params": {
1825                    "token": "index",
1826                    "value": {
1827                        "kind": "begin",
1828                        "title": "Indexing",
1829                        "message": "1/2 crates",
1830                        "percentage": 50
1831                    }
1832                }
1833            }),
1834            &mut tracker,
1835            &sender,
1836        );
1837        assert_eq!(
1838            receiver.try_recv().unwrap(),
1839            LspStatusUpdate::Progress {
1840                title: "Indexing".to_owned(),
1841                message: Some("1/2 crates".to_owned()),
1842                percentage: Some(50),
1843            }
1844        );
1845
1846        handle_server_notification(
1847            &json!({
1848                "method": "$/progress",
1849                "params": {
1850                    "token": "index",
1851                    "value": { "kind": "end", "message": "Indexed" }
1852                }
1853            }),
1854            &mut tracker,
1855            &sender,
1856        );
1857        assert_eq!(
1858            receiver.try_recv().unwrap(),
1859            LspStatusUpdate::Ready {
1860                message: Some("Indexed".to_owned())
1861            }
1862        );
1863    }
1864
1865    #[test]
1866    fn translates_rust_analyzer_server_status() {
1867        let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
1868        let mut tracker = LspProgressTracker::default();
1869        handle_server_notification(
1870            &json!({
1871                "method": "experimental/serverStatus",
1872                "params": {
1873                    "health": "warning",
1874                    "quiescent": true,
1875                    "message": "proc macro unavailable"
1876                }
1877            }),
1878            &mut tracker,
1879            &sender,
1880        );
1881
1882        assert_eq!(
1883            receiver.try_recv().unwrap(),
1884            LspStatusUpdate::Warning("proc macro unavailable".to_owned())
1885        );
1886    }
1887
1888    #[tokio::test]
1889    async fn handles_server_requests_while_waiting_for_symbols() {
1890        let (client_stream, server_stream) = duplex(8 * 1024);
1891        let (client_reader, client_writer) = split(client_stream);
1892        let (server_reader, mut server_writer) = split(server_stream);
1893        let workspace_uri = Url::parse("file:///workspace").unwrap();
1894        let (client, _status_receiver, connection_task) = spawn_json_rpc(
1895            BufReader::new(client_reader),
1896            client_writer,
1897            workspace_uri,
1898            "workspace".to_owned(),
1899        );
1900
1901        let mut server_reader = BufReader::new(server_reader);
1902        write_message(
1903            &mut server_writer,
1904            &json!({
1905                "jsonrpc": "2.0",
1906                "id": "server-request",
1907                "method": "workspace/configuration",
1908                "params": { "items": [{}, {}] },
1909            }),
1910        )
1911        .await
1912        .unwrap();
1913        let configuration_response = read_message(&mut server_reader).await.unwrap();
1914        assert_eq!(configuration_response["id"], "server-request");
1915        assert_eq!(configuration_response["result"], json!([null, null]));
1916
1917        let query_client = client.clone();
1918        let client_task = tokio::spawn(async move {
1919            let response: Option<WorkspaceSymbolResponse> = query_client
1920                .request(
1921                    "workspace/symbol",
1922                    WorkspaceSymbolParams {
1923                        query: "run".to_owned(),
1924                        ..WorkspaceSymbolParams::default()
1925                    },
1926                )
1927                .await
1928                .unwrap();
1929            normalize_symbols(response.unwrap(), SymbolNameAdapter::Standard)
1930        });
1931
1932        let request = read_message(&mut server_reader).await.unwrap();
1933        assert_eq!(request["method"], "workspace/symbol");
1934        assert_eq!(request["params"]["query"], "run");
1935        let request_id = response_id(&request).unwrap();
1936
1937        write_message(
1938            &mut server_writer,
1939            &json!({
1940                "jsonrpc": "2.0",
1941                "method": "window/logMessage",
1942                "params": { "type": 3, "message": "indexed" },
1943            }),
1944        )
1945        .await
1946        .unwrap();
1947        write_message(
1948            &mut server_writer,
1949            &json!({
1950                "jsonrpc": "2.0",
1951                "id": request_id,
1952                "result": [{
1953                    "name": "run",
1954                    "kind": 12,
1955                    "location": {
1956                        "uri": "file:///workspace/src/main.rs",
1957                        "range": {
1958                            "start": { "line": 4, "character": 3 },
1959                            "end": { "line": 4, "character": 6 }
1960                        }
1961                    },
1962                    "containerName": "App"
1963                }]
1964            }),
1965        )
1966        .await
1967        .unwrap();
1968
1969        let symbols = client_task.await.unwrap();
1970        assert_eq!(symbols.len(), 1);
1971        assert_eq!(symbols[0].name, "run");
1972        assert_eq!(symbols[0].kind, SymbolKind::FUNCTION);
1973        assert_eq!(symbols[0].container_name.as_deref(), Some("App"));
1974        assert_eq!(symbols[0].uri.as_str(), "file:///workspace/src/main.rs");
1975        assert_eq!(symbols[0].range.unwrap().start.line, 4);
1976
1977        drop(client);
1978        connection_task.abort();
1979        let _ = connection_task.await;
1980    }
1981
1982    #[tokio::test]
1983    async fn cancels_an_lsp_request_when_its_future_is_dropped() {
1984        let (client_stream, server_stream) = duplex(8 * 1024);
1985        let (client_reader, client_writer) = split(client_stream);
1986        let (server_reader, _server_writer) = split(server_stream);
1987        let workspace_uri = Url::parse("file:///workspace").unwrap();
1988        let (client, _status_receiver, connection_task) = spawn_json_rpc(
1989            BufReader::new(client_reader),
1990            client_writer,
1991            workspace_uri,
1992            "workspace".to_owned(),
1993        );
1994        let mut server_reader = BufReader::new(server_reader);
1995
1996        let query_client = client.clone();
1997        let client_task = tokio::spawn(async move {
1998            let _: Option<WorkspaceSymbolResponse> = query_client
1999                .request(
2000                    "workspace/symbol",
2001                    WorkspaceSymbolParams {
2002                        query: "first".to_owned(),
2003                        ..WorkspaceSymbolParams::default()
2004                    },
2005                )
2006                .await
2007                .unwrap();
2008        });
2009
2010        let request = read_message(&mut server_reader).await.unwrap();
2011        let request_id = response_id(&request).unwrap();
2012        client_task.abort();
2013        let _ = client_task.await;
2014
2015        let cancellation = timeout(Duration::from_secs(1), read_message(&mut server_reader))
2016            .await
2017            .expect("client did not send $/cancelRequest")
2018            .unwrap();
2019        assert_eq!(cancellation["method"], "$/cancelRequest");
2020        assert_eq!(cancellation["params"]["id"], request_id);
2021
2022        drop(client);
2023        connection_task.abort();
2024        let _ = connection_task.await;
2025    }
2026
2027    #[tokio::test]
2028    async fn rejects_oversized_messages() {
2029        use tokio::io::AsyncWriteExt;
2030
2031        let (mut client_stream, server_stream) = duplex(128);
2032        let _server_task = tokio::spawn(async move {
2033            client_stream
2034                .write_all(b"Content-Length: 16777217\r\n\r\n")
2035                .await
2036                .unwrap();
2037        });
2038
2039        let error = read_message(&mut BufReader::new(server_stream))
2040            .await
2041            .unwrap_err();
2042        assert!(error.to_string().contains("too large"));
2043    }
2044
2045    fn symbol(uri: &str) -> WorkspaceSymbolMatch {
2046        WorkspaceSymbolMatch {
2047            name: "symbol".to_owned(),
2048            kind: SymbolKind::FUNCTION,
2049            container_name: None,
2050            uri: Url::parse(uri).unwrap(),
2051            range: None,
2052        }
2053    }
2054
2055    fn call_item(name: &str, line: u32) -> Value {
2056        json!({
2057            "name": name,
2058            "kind": 12,
2059            "uri": "file:///workspace/src/main.rs",
2060            "range": {
2061                "start": { "line": line, "character": 0 },
2062                "end": { "line": line, "character": name.len() }
2063            },
2064            "selectionRange": {
2065                "start": { "line": line, "character": 0 },
2066                "end": { "line": line, "character": name.len() }
2067            }
2068        })
2069    }
2070
2071    fn rust_method_item(name: &str, line: u32) -> Value {
2072        let mut item = call_item(name, line);
2073        item["detail"] = json!(format!("pub fn {name}(&self)"));
2074        item
2075    }
2076
2077    fn type_item(name: &str, line: u32) -> Value {
2078        json!({
2079            "name": name,
2080            "kind": 23,
2081            "uri": "file:///workspace/src/main.rs",
2082            "range": {
2083                "start": { "line": line, "character": 0 },
2084                "end": { "line": line, "character": name.len() }
2085            },
2086            "selectionRange": {
2087                "start": { "line": line, "character": 0 },
2088                "end": { "line": line, "character": name.len() }
2089            }
2090        })
2091    }
2092}