1mod 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
56const 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)]
99pub 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 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 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 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 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}