⭐️ A friendly language for building type-safe, scalable systems!
at main 1597 lines 61 kB view raw
1use crate::{ 2 Error, Result, Warning, 3 analyse::name::correct_name_case, 4 ast::{ 5 self, Constant, CustomType, Definition, DefinitionLocation, ModuleConstant, 6 PatternUnusedArguments, SrcSpan, TypedArg, TypedConstant, TypedExpr, TypedFunction, 7 TypedModule, TypedPattern, 8 }, 9 build::{ 10 ExpressionPosition, Located, Module, UnqualifiedImport, type_constructor_from_modules, 11 }, 12 config::PackageConfig, 13 io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter}, 14 language_server::{ 15 compiler::LspProjectCompiler, files::FileSystemProxy, progress::ProgressReporter, 16 }, 17 line_numbers::LineNumbers, 18 paths::ProjectPaths, 19 type_::{ 20 self, Deprecation, ModuleInterface, Type, TypeConstructor, ValueConstructor, 21 ValueConstructorVariant, 22 error::{Named, VariableSyntax}, 23 printer::Printer, 24 }, 25}; 26use camino::Utf8PathBuf; 27use ecow::EcoString; 28use itertools::Itertools; 29use lsp::CodeAction; 30use lsp_types::{ 31 self as lsp, DocumentSymbol, Hover, HoverContents, MarkedString, Position, 32 PrepareRenameResponse, Range, SignatureHelp, SymbolKind, SymbolTag, TextEdit, Url, 33 WorkspaceEdit, 34}; 35use std::{collections::HashSet, sync::Arc}; 36 37use super::{ 38 DownloadDependencies, MakeLocker, 39 code_action::{ 40 AddAnnotations, CodeActionBuilder, ConvertFromUse, ConvertToFunctionCall, ConvertToPipe, 41 ConvertToUse, ExpandFunctionCapture, ExtractConstant, ExtractVariable, 42 FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation, 43 FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder, 44 GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, PatternMatchOnValue, 45 RedundantTupleInCaseSubject, RemoveEchos, RemoveUnusedImports, UseLabelShorthandSyntax, 46 WrapInBlock, code_action_add_missing_patterns, 47 code_action_convert_qualified_constructor_to_unqualified, 48 code_action_convert_unqualified_constructor_to_qualified, code_action_import_module, 49 code_action_inexhaustive_let_to_case, 50 }, 51 completer::Completer, 52 reference::{ 53 Referenced, VariableReferenceKind, find_module_references, find_variable_references, 54 reference_for_ast_node, 55 }, 56 rename::{RenameTarget, Renamed, rename_local_variable, rename_module_entity}, 57 signature_help, src_span_to_lsp_range, 58}; 59 60#[derive(Debug, PartialEq, Eq)] 61pub struct Response<T> { 62 pub result: Result<T, Error>, 63 pub warnings: Vec<Warning>, 64 pub compilation: Compilation, 65} 66 67#[derive(Debug, PartialEq, Eq)] 68pub enum Compilation { 69 /// Compilation was attempted and succeeded for these modules. 70 Yes(Vec<Utf8PathBuf>), 71 /// Compilation was not attempted for this operation. 72 No, 73} 74 75#[derive(Debug)] 76pub struct LanguageServerEngine<IO, Reporter> { 77 pub(crate) paths: ProjectPaths, 78 79 /// A compiler for the project that supports repeat compilation of the root 80 /// package. 81 /// In the event the project config changes this will need to be 82 /// discarded and reloaded to handle any changes to dependencies. 83 pub(crate) compiler: LspProjectCompiler<FileSystemProxy<IO>>, 84 85 modules_compiled_since_last_feedback: Vec<Utf8PathBuf>, 86 compiled_since_last_feedback: bool, 87 error: Option<Error>, 88 89 // Used to publish progress notifications to the client without waiting for 90 // the usual request-response loop. 91 progress_reporter: Reporter, 92 93 /// Used to know if to show the "View on HexDocs" link 94 /// when hovering on an imported value 95 hex_deps: HashSet<EcoString>, 96} 97 98impl<'a, IO, Reporter> LanguageServerEngine<IO, Reporter> 99where 100 // IO to be supplied from outside of gleam-core 101 IO: FileSystemReader 102 + FileSystemWriter 103 + BeamCompiler 104 + CommandExecutor 105 + DownloadDependencies 106 + MakeLocker 107 + Clone, 108 // IO to be supplied from inside of gleam-core 109 Reporter: ProgressReporter + Clone + 'a, 110{ 111 pub fn new( 112 config: PackageConfig, 113 progress_reporter: Reporter, 114 io: FileSystemProxy<IO>, 115 paths: ProjectPaths, 116 ) -> Result<Self> { 117 let locker = io.inner().make_locker(&paths, config.target)?; 118 119 // Download dependencies to ensure they are up-to-date for this new 120 // configuration and new instance of the compiler 121 progress_reporter.dependency_downloading_started(); 122 let manifest = io.inner().download_dependencies(&paths); 123 progress_reporter.dependency_downloading_finished(); 124 125 // NOTE: This must come after the progress reporter has finished! 126 let manifest = manifest?; 127 128 let compiler: LspProjectCompiler<FileSystemProxy<IO>> = 129 LspProjectCompiler::new(manifest, config, paths.clone(), io.clone(), locker)?; 130 131 let hex_deps = compiler 132 .project_compiler 133 .packages 134 .iter() 135 .flat_map(|(k, v)| match &v.source { 136 crate::manifest::ManifestPackageSource::Hex { .. } => { 137 Some(EcoString::from(k.as_str())) 138 } 139 140 _ => None, 141 }) 142 .collect(); 143 144 Ok(Self { 145 modules_compiled_since_last_feedback: vec![], 146 compiled_since_last_feedback: false, 147 progress_reporter, 148 compiler, 149 paths, 150 error: None, 151 hex_deps, 152 }) 153 } 154 155 pub fn compile_please(&mut self) -> Response<()> { 156 self.respond(Self::compile) 157 } 158 159 /// Compile the project if we are in one. Otherwise do nothing. 160 fn compile(&mut self) -> Result<(), Error> { 161 self.compiled_since_last_feedback = true; 162 163 self.progress_reporter.compilation_started(); 164 let outcome = self.compiler.compile(); 165 self.progress_reporter.compilation_finished(); 166 167 let result = outcome 168 // Register which modules have changed 169 .map(|modules| self.modules_compiled_since_last_feedback.extend(modules)) 170 // Return the error, if present 171 .into_result(); 172 173 self.error = match &result { 174 Ok(_) => None, 175 Err(error) => Some(error.clone()), 176 }; 177 178 result 179 } 180 181 fn take_warnings(&mut self) -> Vec<Warning> { 182 self.compiler.take_warnings() 183 } 184 185 // TODO: implement unqualified imported module functions 186 // 187 pub fn goto_definition( 188 &mut self, 189 params: lsp::GotoDefinitionParams, 190 ) -> Response<Option<lsp::Location>> { 191 self.respond(|this| { 192 let params = params.text_document_position_params; 193 let (line_numbers, node) = match this.node_at_position(&params) { 194 Some(location) => location, 195 None => return Ok(None), 196 }; 197 198 let Some(location) = 199 node.definition_location(this.compiler.project_compiler.get_importable_modules()) 200 else { 201 return Ok(None); 202 }; 203 204 Ok(this.definition_location_to_lsp_location(&line_numbers, &params, location)) 205 }) 206 } 207 208 pub(crate) fn goto_type_definition( 209 &mut self, 210 params: lsp_types::GotoDefinitionParams, 211 ) -> Response<Vec<lsp::Location>> { 212 self.respond(|this| { 213 let params = params.text_document_position_params; 214 let (line_numbers, node) = match this.node_at_position(&params) { 215 Some(location) => location, 216 None => return Ok(vec![]), 217 }; 218 219 let Some(locations) = node 220 .type_definition_locations(this.compiler.project_compiler.get_importable_modules()) 221 else { 222 return Ok(vec![]); 223 }; 224 225 let locations = locations 226 .into_iter() 227 .filter_map(|location| { 228 this.definition_location_to_lsp_location(&line_numbers, &params, location) 229 }) 230 .collect_vec(); 231 232 Ok(locations) 233 }) 234 } 235 236 fn definition_location_to_lsp_location( 237 &self, 238 line_numbers: &LineNumbers, 239 params: &lsp_types::TextDocumentPositionParams, 240 location: DefinitionLocation, 241 ) -> Option<lsp::Location> { 242 let (uri, line_numbers) = match location.module { 243 None => (params.text_document.uri.clone(), line_numbers), 244 Some(name) => { 245 let module = self.compiler.get_source(&name)?; 246 let url = Url::parse(&format!("file:///{}", &module.path)) 247 .expect("goto definition URL parse"); 248 (url, &module.line_numbers) 249 } 250 }; 251 let range = src_span_to_lsp_range(location.span, line_numbers); 252 253 Some(lsp::Location { uri, range }) 254 } 255 256 pub fn completion( 257 &mut self, 258 params: lsp::TextDocumentPositionParams, 259 src: EcoString, 260 ) -> Response<Option<Vec<lsp::CompletionItem>>> { 261 self.respond(|this| { 262 let module = match this.module_for_uri(&params.text_document.uri) { 263 Some(m) => m, 264 None => return Ok(None), 265 }; 266 267 let completer = Completer::new(&src, &params, &this.compiler, module); 268 let byte_index = completer.module_line_numbers.byte_index(params.position); 269 270 // If in comment context, do not provide completions 271 if module.extra.is_within_comment(byte_index) { 272 return Ok(None); 273 } 274 275 // Check current filercontents if the user is writing an import 276 // and handle separately from the rest of the completion flow 277 // Check if an import is being written 278 if let Some(value) = completer.import_completions() { 279 return value; 280 } 281 282 let Some(found) = module.find_node(byte_index) else { 283 return Ok(None); 284 }; 285 286 let completions = match found { 287 Located::PatternSpread { .. } => None, 288 Located::Pattern(_pattern) => None, 289 // Do not show completions when typing inside a string. 290 Located::Expression { 291 expression: TypedExpr::String { .. }, 292 .. 293 } 294 | Located::Constant(Constant::String { .. }) => None, 295 Located::Expression { 296 expression: TypedExpr::Call { fun, args, .. }, 297 .. 298 } => { 299 let mut completions = vec![]; 300 completions.append(&mut completer.completion_values()); 301 completions.append(&mut completer.completion_labels(fun, args)); 302 Some(completions) 303 } 304 Located::Expression { 305 expression: TypedExpr::RecordAccess { record, .. }, 306 .. 307 } => { 308 let mut completions = vec![]; 309 completions.append(&mut completer.completion_values()); 310 completions.append(&mut completer.completion_field_accessors(record.type_())); 311 Some(completions) 312 } 313 Located::Expression { 314 position: 315 ExpressionPosition::ArgumentOrLabel { 316 called_function, 317 function_arguments, 318 }, 319 .. 320 } => { 321 let mut completions = vec![]; 322 completions.append(&mut completer.completion_values()); 323 completions.append( 324 &mut completer.completion_labels(called_function, function_arguments), 325 ); 326 Some(completions) 327 } 328 Located::Statement(_) | Located::Expression { .. } => { 329 Some(completer.completion_values()) 330 } 331 Located::ModuleStatement(Definition::Function(_)) => { 332 Some(completer.completion_types()) 333 } 334 335 Located::FunctionBody(_) => Some(completer.completion_values()), 336 337 Located::ModuleStatement(Definition::TypeAlias(_) | Definition::CustomType(_)) 338 | Located::VariantConstructorDefinition(_) => Some(completer.completion_types()), 339 340 // If the import completions returned no results and we are in an import then 341 // we should try to provide completions for unqualified values 342 Located::ModuleStatement(Definition::Import(import)) => this 343 .compiler 344 .get_module_interface(import.module.as_str()) 345 .map(|importing_module| { 346 completer.unqualified_completions_from_module(importing_module, true) 347 }), 348 349 Located::ModuleStatement(Definition::ModuleConstant(_)) | Located::Constant(_) => { 350 Some(completer.completion_values()) 351 } 352 353 Located::UnqualifiedImport(_) => None, 354 355 Located::Arg(_) => None, 356 357 Located::Annotation { .. } => Some(completer.completion_types()), 358 359 Located::Label(_, _) => None, 360 361 Located::ModuleName { 362 layer: ast::Layer::Type, 363 .. 364 } => Some(completer.completion_types()), 365 Located::ModuleName { 366 layer: ast::Layer::Value, 367 .. 368 } => Some(completer.completion_values()), 369 }; 370 371 Ok(completions) 372 }) 373 } 374 375 pub fn code_actions( 376 &mut self, 377 params: lsp::CodeActionParams, 378 ) -> Response<Option<Vec<CodeAction>>> { 379 self.respond(|this| { 380 let mut actions = vec![]; 381 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 382 return Ok(None); 383 }; 384 385 let lines = LineNumbers::new(&module.code); 386 387 code_action_unused_values(module, &lines, &params, &mut actions); 388 actions.extend(RemoveUnusedImports::new(module, &lines, &params).code_actions()); 389 code_action_convert_qualified_constructor_to_unqualified( 390 module, 391 &lines, 392 &params, 393 &mut actions, 394 ); 395 code_action_convert_unqualified_constructor_to_qualified( 396 module, 397 &lines, 398 &params, 399 &mut actions, 400 ); 401 code_action_fix_names(&lines, &params, &this.error, &mut actions); 402 code_action_import_module(module, &lines, &params, &this.error, &mut actions); 403 code_action_add_missing_patterns(module, &lines, &params, &this.error, &mut actions); 404 code_action_inexhaustive_let_to_case( 405 module, 406 &lines, 407 &params, 408 &this.error, 409 &mut actions, 410 ); 411 actions.extend(FixBinaryOperation::new(module, &lines, &params).code_actions()); 412 actions 413 .extend(FixTruncatedBitArraySegment::new(module, &lines, &params).code_actions()); 414 actions.extend(LetAssertToCase::new(module, &lines, &params).code_actions()); 415 actions 416 .extend(RedundantTupleInCaseSubject::new(module, &lines, &params).code_actions()); 417 actions.extend(UseLabelShorthandSyntax::new(module, &lines, &params).code_actions()); 418 actions.extend(FillInMissingLabelledArgs::new(module, &lines, &params).code_actions()); 419 actions.extend(ConvertFromUse::new(module, &lines, &params).code_actions()); 420 actions.extend(RemoveEchos::new(module, &lines, &params).code_actions()); 421 actions.extend(ConvertToUse::new(module, &lines, &params).code_actions()); 422 actions.extend(ExpandFunctionCapture::new(module, &lines, &params).code_actions()); 423 actions.extend(FillUnusedFields::new(module, &lines, &params).code_actions()); 424 actions.extend(InterpolateString::new(module, &lines, &params).code_actions()); 425 actions.extend(ExtractVariable::new(module, &lines, &params).code_actions()); 426 actions.extend(ExtractConstant::new(module, &lines, &params).code_actions()); 427 actions.extend(GenerateFunction::new(module, &lines, &params).code_actions()); 428 actions.extend( 429 GenerateVariant::new(module, &this.compiler, &lines, &params).code_actions(), 430 ); 431 actions.extend(ConvertToPipe::new(module, &lines, &params).code_actions()); 432 actions.extend(ConvertToFunctionCall::new(module, &lines, &params).code_actions()); 433 actions.extend( 434 PatternMatchOnValue::new(module, &lines, &params, &this.compiler).code_actions(), 435 ); 436 actions.extend(InlineVariable::new(module, &lines, &params).code_actions()); 437 actions.extend(WrapInBlock::new(module, &lines, &params).code_actions()); 438 GenerateDynamicDecoder::new(module, &lines, &params, &mut actions).code_actions(); 439 GenerateJsonEncoder::new( 440 module, 441 &lines, 442 &params, 443 &mut actions, 444 &this.compiler.project_compiler.config, 445 ) 446 .code_actions(); 447 AddAnnotations::new(module, &lines, &params).code_action(&mut actions); 448 Ok(if actions.is_empty() { 449 None 450 } else { 451 Some(actions) 452 }) 453 }) 454 } 455 456 pub fn document_symbol( 457 &mut self, 458 params: lsp::DocumentSymbolParams, 459 ) -> Response<Vec<DocumentSymbol>> { 460 self.respond(|this| { 461 let mut symbols = vec![]; 462 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 463 return Ok(symbols); 464 }; 465 let line_numbers = LineNumbers::new(&module.code); 466 467 for definition in &module.ast.definitions { 468 match definition { 469 // Typically, imports aren't considered document symbols. 470 Definition::Import(_) => {} 471 472 Definition::Function(function) => { 473 // By default, the function's location ends right after the return type. 474 // For the full symbol range, have it end at the end of the body. 475 // Also include the documentation, if available. 476 // 477 // By convention, the symbol span starts from the leading slash in the 478 // documentation comment's marker ('///'), not from its content (of which 479 // we have the position), so we must convert the content start position 480 // to the leading slash's position using 'get_doc_marker_pos'. 481 let full_function_span = SrcSpan { 482 start: function 483 .documentation 484 .as_ref() 485 .map(|(doc_start, _)| get_doc_marker_pos(*doc_start)) 486 .unwrap_or(function.location.start), 487 488 end: function.end_position, 489 }; 490 491 let (name_location, name) = function 492 .name 493 .as_ref() 494 .expect("Function in a definition must be named"); 495 496 // The 'deprecated' field is deprecated, but we have to specify it anyway 497 // to be able to construct the 'DocumentSymbol' type, so 498 // we suppress the warning. We specify 'None' as specifying 'Some' 499 // is what is actually deprecated. 500 #[allow(deprecated)] 501 symbols.push(DocumentSymbol { 502 name: name.to_string(), 503 detail: Some( 504 Printer::new(&module.ast.names) 505 .print_type(&get_function_type(function)) 506 .to_string(), 507 ), 508 kind: SymbolKind::FUNCTION, 509 tags: make_deprecated_symbol_tag(&function.deprecation), 510 deprecated: None, 511 range: src_span_to_lsp_range(full_function_span, &line_numbers), 512 selection_range: src_span_to_lsp_range(*name_location, &line_numbers), 513 children: None, 514 }); 515 } 516 517 Definition::TypeAlias(alias) => { 518 let full_alias_span = match alias.documentation { 519 Some((doc_position, _)) => { 520 SrcSpan::new(get_doc_marker_pos(doc_position), alias.location.end) 521 } 522 None => alias.location, 523 }; 524 525 // The 'deprecated' field is deprecated, but we have to specify it anyway 526 // to be able to construct the 'DocumentSymbol' type, so 527 // we suppress the warning. We specify 'None' as specifying 'Some' 528 // is what is actually deprecated. 529 #[allow(deprecated)] 530 symbols.push(DocumentSymbol { 531 name: alias.alias.to_string(), 532 detail: Some( 533 Printer::new(&module.ast.names) 534 // If we print with aliases, we end up printing the alias which the user 535 // is currently hovering, which is not helpful. Instead, we print the 536 // raw type, so the user can see which type the alias represents 537 .print_type_without_aliases(&alias.type_) 538 .to_string(), 539 ), 540 kind: SymbolKind::CLASS, 541 tags: make_deprecated_symbol_tag(&alias.deprecation), 542 deprecated: None, 543 range: src_span_to_lsp_range(full_alias_span, &line_numbers), 544 selection_range: src_span_to_lsp_range( 545 alias.name_location, 546 &line_numbers, 547 ), 548 children: None, 549 }); 550 } 551 552 Definition::CustomType(type_) => { 553 symbols.push(custom_type_symbol(type_, &line_numbers, module)); 554 } 555 556 Definition::ModuleConstant(constant) => { 557 // `ModuleConstant.location` ends at the constant's name or type. 558 // For the full symbol span, necessary for `range`, we need to 559 // include the constant value as well. 560 // Also include the documentation at the start, if available. 561 let full_constant_span = SrcSpan { 562 start: constant 563 .documentation 564 .as_ref() 565 .map(|(doc_start, _)| get_doc_marker_pos(*doc_start)) 566 .unwrap_or(constant.location.start), 567 568 end: constant.value.location().end, 569 }; 570 571 // The 'deprecated' field is deprecated, but we have to specify it anyway 572 // to be able to construct the 'DocumentSymbol' type, so 573 // we suppress the warning. We specify 'None' as specifying 'Some' 574 // is what is actually deprecated. 575 #[allow(deprecated)] 576 symbols.push(DocumentSymbol { 577 name: constant.name.to_string(), 578 detail: Some( 579 Printer::new(&module.ast.names) 580 .print_type(&constant.type_) 581 .to_string(), 582 ), 583 kind: SymbolKind::CONSTANT, 584 tags: make_deprecated_symbol_tag(&constant.deprecation), 585 deprecated: None, 586 range: src_span_to_lsp_range(full_constant_span, &line_numbers), 587 selection_range: src_span_to_lsp_range( 588 constant.name_location, 589 &line_numbers, 590 ), 591 children: None, 592 }); 593 } 594 } 595 } 596 597 Ok(symbols) 598 }) 599 } 600 601 /// Check whether a particular module is in the same package as this one 602 fn is_same_package(&self, current_module: &Module, module_name: &str) -> bool { 603 let other_module = self 604 .compiler 605 .project_compiler 606 .get_importable_modules() 607 .get(module_name); 608 match other_module { 609 // We can't rename values from other packages if we are not aliasing an unqualified import. 610 Some(module) => module.package == current_module.ast.type_info.package, 611 None => false, 612 } 613 } 614 615 pub fn prepare_rename( 616 &mut self, 617 params: lsp::TextDocumentPositionParams, 618 ) -> Response<Option<PrepareRenameResponse>> { 619 self.respond(|this| { 620 let (lines, found) = match this.node_at_position(&params) { 621 Some(value) => value, 622 None => return Ok(None), 623 }; 624 625 let Some(current_module) = this.module_for_uri(&params.text_document.uri) else { 626 return Ok(None); 627 }; 628 629 let success_response = |location| { 630 Some(PrepareRenameResponse::Range(src_span_to_lsp_range( 631 location, &lines, 632 ))) 633 }; 634 635 let byte_index = lines.byte_index(params.position); 636 637 Ok(match reference_for_ast_node(found, &current_module.name) { 638 Some(Referenced::LocalVariable { 639 location, origin, .. 640 }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { 641 Some(VariableSyntax::Generated) => None, 642 Some( 643 VariableSyntax::Variable { .. } 644 | VariableSyntax::AssignmentPattern 645 | VariableSyntax::LabelShorthand(_), 646 ) 647 | None => success_response(location), 648 }, 649 Some( 650 Referenced::ModuleValue { 651 module, 652 location, 653 target_kind, 654 .. 655 } 656 | Referenced::ModuleType { 657 module, 658 location, 659 target_kind, 660 .. 661 }, 662 ) if location.contains(byte_index) => { 663 // We can't rename types or values from other packages if we are not aliasing an unqualified import. 664 let rename_allowed = match target_kind { 665 RenameTarget::Qualified => this.is_same_package(current_module, &module), 666 RenameTarget::Unqualified | RenameTarget::Definition => true, 667 }; 668 if rename_allowed { 669 success_response(location) 670 } else { 671 None 672 } 673 } 674 _ => None, 675 }) 676 }) 677 } 678 679 pub fn rename(&mut self, params: lsp::RenameParams) -> Response<Option<WorkspaceEdit>> { 680 self.respond(|this| { 681 let position = &params.text_document_position; 682 683 let (lines, found) = match this.node_at_position(position) { 684 Some(value) => value, 685 None => return Ok(None), 686 }; 687 688 let Some(module) = this.module_for_uri(&position.text_document.uri) else { 689 return Ok(None); 690 }; 691 692 Ok(match reference_for_ast_node(found, &module.name) { 693 Some(Referenced::LocalVariable { 694 origin, 695 definition_location, 696 .. 697 }) => { 698 let rename_kind = match origin.map(|origin| origin.syntax) { 699 Some(VariableSyntax::Generated) => return Ok(None), 700 Some(VariableSyntax::LabelShorthand(_)) => { 701 VariableReferenceKind::LabelShorthand 702 } 703 Some( 704 VariableSyntax::AssignmentPattern | VariableSyntax::Variable { .. }, 705 ) 706 | None => VariableReferenceKind::Variable, 707 }; 708 rename_local_variable(module, &lines, &params, definition_location, rename_kind) 709 } 710 Some(Referenced::ModuleValue { 711 module: module_name, 712 target_kind, 713 name, 714 name_kind, 715 .. 716 }) => rename_module_entity( 717 &params, 718 module, 719 this.compiler.project_compiler.get_importable_modules(), 720 &this.compiler.sources, 721 Renamed { 722 module_name: &module_name, 723 name: &name, 724 name_kind, 725 target_kind, 726 layer: ast::Layer::Value, 727 }, 728 ), 729 Some(Referenced::ModuleType { 730 module: module_name, 731 target_kind, 732 name, 733 .. 734 }) => rename_module_entity( 735 &params, 736 module, 737 this.compiler.project_compiler.get_importable_modules(), 738 &this.compiler.sources, 739 Renamed { 740 module_name: &module_name, 741 name: &name, 742 name_kind: Named::Type, 743 target_kind, 744 layer: ast::Layer::Type, 745 }, 746 ), 747 None => None, 748 }) 749 }) 750 } 751 752 pub fn find_references( 753 &mut self, 754 params: lsp::ReferenceParams, 755 ) -> Response<Option<Vec<lsp::Location>>> { 756 self.respond(|this| { 757 let position = &params.text_document_position; 758 759 let (lines, found) = match this.node_at_position(position) { 760 Some(value) => value, 761 None => return Ok(None), 762 }; 763 764 let uri = position.text_document.uri.clone(); 765 766 let Some(module) = this.module_for_uri(&uri) else { 767 return Ok(None); 768 }; 769 770 let byte_index = lines.byte_index(position.position); 771 772 Ok(match reference_for_ast_node(found, &module.name) { 773 Some(Referenced::LocalVariable { 774 origin, 775 definition_location, 776 location, 777 }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { 778 Some(VariableSyntax::Generated) => None, 779 Some( 780 VariableSyntax::LabelShorthand(_) 781 | VariableSyntax::AssignmentPattern 782 | VariableSyntax::Variable { .. }, 783 ) 784 | None => { 785 let variable_references = 786 find_variable_references(&module.ast, definition_location); 787 788 let mut reference_locations = 789 Vec::with_capacity(variable_references.len() + 1); 790 reference_locations.push(lsp::Location { 791 uri: uri.clone(), 792 range: src_span_to_lsp_range(definition_location, &lines), 793 }); 794 795 for reference in variable_references { 796 reference_locations.push(lsp::Location { 797 uri: uri.clone(), 798 range: src_span_to_lsp_range(reference.location, &lines), 799 }) 800 } 801 802 Some(reference_locations) 803 } 804 }, 805 Some(Referenced::ModuleValue { 806 module, 807 name, 808 location, 809 .. 810 }) if location.contains(byte_index) => Some(find_module_references( 811 module, 812 name, 813 this.compiler.project_compiler.get_importable_modules(), 814 &this.compiler.sources, 815 ast::Layer::Value, 816 )), 817 Some(Referenced::ModuleType { 818 module, 819 name, 820 location, 821 .. 822 }) if location.contains(byte_index) => Some(find_module_references( 823 module, 824 name, 825 this.compiler.project_compiler.get_importable_modules(), 826 &this.compiler.sources, 827 ast::Layer::Type, 828 )), 829 _ => None, 830 }) 831 }) 832 } 833 834 fn respond<T>(&mut self, handler: impl FnOnce(&mut Self) -> Result<T>) -> Response<T> { 835 let result = handler(self); 836 let warnings = self.take_warnings(); 837 // TODO: test. Ensure hover doesn't report as compiled 838 let compilation = if self.compiled_since_last_feedback { 839 let modules = std::mem::take(&mut self.modules_compiled_since_last_feedback); 840 self.compiled_since_last_feedback = false; 841 Compilation::Yes(modules) 842 } else { 843 Compilation::No 844 }; 845 Response { 846 result, 847 warnings, 848 compilation, 849 } 850 } 851 852 pub fn hover(&mut self, params: lsp::HoverParams) -> Response<Option<Hover>> { 853 self.respond(|this| { 854 let params = params.text_document_position_params; 855 856 let (lines, found) = match this.node_at_position(&params) { 857 Some(value) => value, 858 None => return Ok(None), 859 }; 860 861 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 862 return Ok(None); 863 }; 864 865 Ok(match found { 866 Located::Statement(_) => None, // TODO: hover for statement 867 Located::ModuleStatement(Definition::Function(fun)) => { 868 Some(hover_for_function_head(fun, lines, module)) 869 } 870 Located::ModuleStatement(Definition::ModuleConstant(constant)) => { 871 Some(hover_for_module_constant(constant, lines, module)) 872 } 873 Located::Constant(constant) => Some(hover_for_constant(constant, lines, module)), 874 Located::ModuleStatement(Definition::Import(import)) => { 875 let Some(module) = this.compiler.get_module_interface(&import.module) else { 876 return Ok(None); 877 }; 878 Some(hover_for_module( 879 module, 880 import.location, 881 &lines, 882 &this.hex_deps, 883 )) 884 } 885 Located::ModuleStatement(_) => None, 886 Located::VariantConstructorDefinition(_) => None, 887 Located::UnqualifiedImport(UnqualifiedImport { 888 name, 889 module: module_name, 890 is_type, 891 location, 892 }) => this 893 .compiler 894 .get_module_interface(module_name.as_str()) 895 .and_then(|module_interface| { 896 if is_type { 897 module_interface.types.get(name).map(|t| { 898 hover_for_annotation( 899 *location, 900 t.type_.as_ref(), 901 Some(t), 902 lines, 903 module, 904 ) 905 }) 906 } else { 907 module_interface.values.get(name).map(|v| { 908 let m = if this.hex_deps.contains(&module_interface.package) { 909 Some(module_interface) 910 } else { 911 None 912 }; 913 hover_for_imported_value(v, location, lines, m, name, module) 914 }) 915 } 916 }), 917 Located::Pattern(pattern) => Some(hover_for_pattern(pattern, lines, module)), 918 Located::PatternSpread { 919 spread_location, 920 pattern, 921 } => { 922 let range = Some(src_span_to_lsp_range(spread_location, &lines)); 923 924 let mut printer = Printer::new(&module.ast.names); 925 926 let PatternUnusedArguments { 927 positional, 928 labelled, 929 } = pattern.unused_arguments().unwrap_or_default(); 930 931 let positional = positional 932 .iter() 933 .map(|type_| format!("- `{}`", printer.print_type(type_))) 934 .join("\n"); 935 let labelled = labelled 936 .iter() 937 .map(|(label, type_)| { 938 format!("- `{}: {}`", label, printer.print_type(type_)) 939 }) 940 .join("\n"); 941 942 let content = match (positional.is_empty(), labelled.is_empty()) { 943 (true, false) => format!("Unused labelled fields:\n{labelled}"), 944 (false, true) => format!("Unused positional fields:\n{positional}"), 945 (_, _) => format!( 946 "Unused positional fields: 947{positional} 948 949Unused labelled fields: 950{labelled}" 951 ), 952 }; 953 954 Some(Hover { 955 contents: HoverContents::Scalar(MarkedString::from_markdown(content)), 956 range, 957 }) 958 } 959 Located::Expression { expression, .. } => Some(hover_for_expression( 960 expression, 961 lines, 962 module, 963 &this.hex_deps, 964 )), 965 Located::Arg(arg) => Some(hover_for_function_argument(arg, lines, module)), 966 Located::FunctionBody(_) => None, 967 Located::Annotation { ast, type_ } => { 968 let type_constructor = type_constructor_from_modules( 969 this.compiler.project_compiler.get_importable_modules(), 970 type_.clone(), 971 ); 972 Some(hover_for_annotation( 973 ast.location(), 974 &type_, 975 type_constructor, 976 lines, 977 module, 978 )) 979 } 980 Located::Label(location, type_) => { 981 Some(hover_for_label(location, type_, lines, module)) 982 } 983 Located::ModuleName { location, name, .. } => { 984 let Some(module) = this.compiler.get_module_interface(name) else { 985 return Ok(None); 986 }; 987 Some(hover_for_module(module, location, &lines, &this.hex_deps)) 988 } 989 }) 990 }) 991 } 992 993 pub(crate) fn signature_help( 994 &mut self, 995 params: lsp_types::SignatureHelpParams, 996 ) -> Response<Option<SignatureHelp>> { 997 self.respond( 998 |this| match this.node_at_position(&params.text_document_position_params) { 999 Some((_lines, Located::Expression { expression, .. })) => { 1000 Ok(signature_help::for_expression(expression)) 1001 } 1002 Some((_lines, _located)) => Ok(None), 1003 None => Ok(None), 1004 }, 1005 ) 1006 } 1007 1008 fn module_node_at_position( 1009 &self, 1010 params: &lsp::TextDocumentPositionParams, 1011 module: &'a Module, 1012 ) -> Option<(LineNumbers, Located<'a>)> { 1013 let line_numbers = LineNumbers::new(&module.code); 1014 let byte_index = line_numbers.byte_index(params.position); 1015 let node = module.find_node(byte_index); 1016 let node = node?; 1017 Some((line_numbers, node)) 1018 } 1019 1020 fn node_at_position( 1021 &self, 1022 params: &lsp::TextDocumentPositionParams, 1023 ) -> Option<(LineNumbers, Located<'_>)> { 1024 let module = self.module_for_uri(&params.text_document.uri)?; 1025 self.module_node_at_position(params, module) 1026 } 1027 1028 fn module_for_uri(&self, uri: &Url) -> Option<&Module> { 1029 // The to_file_path method is available on these platforms 1030 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] 1031 let path = uri.to_file_path().expect("URL file"); 1032 1033 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))] 1034 let path: Utf8PathBuf = uri.path().into(); 1035 1036 let components = path 1037 .strip_prefix(self.paths.root()) 1038 .ok()? 1039 .components() 1040 .skip(1) 1041 .map(|c| c.as_os_str().to_string_lossy()); 1042 let module_name: EcoString = Itertools::intersperse(components, "/".into()) 1043 .collect::<String>() 1044 .strip_suffix(".gleam")? 1045 .into(); 1046 1047 self.compiler.modules.get(&module_name) 1048 } 1049} 1050 1051fn custom_type_symbol( 1052 type_: &CustomType<Arc<Type>>, 1053 line_numbers: &LineNumbers, 1054 module: &Module, 1055) -> DocumentSymbol { 1056 let constructors = type_ 1057 .constructors 1058 .iter() 1059 .map(|constructor| { 1060 let mut arguments = vec![]; 1061 1062 // List named arguments as field symbols. 1063 for argument in &constructor.arguments { 1064 let Some((label_location, label)) = &argument.label else { 1065 continue; 1066 }; 1067 1068 let full_arg_span = match argument.doc { 1069 Some((doc_position, _)) => { 1070 SrcSpan::new(get_doc_marker_pos(doc_position), argument.location.end) 1071 } 1072 None => argument.location, 1073 }; 1074 1075 // The 'deprecated' field is deprecated, but we have to specify it anyway 1076 // to be able to construct the 'DocumentSymbol' type, so 1077 // we suppress the warning. We specify 'None' as specifying 'Some' 1078 // is what is actually deprecated. 1079 #[allow(deprecated)] 1080 arguments.push(DocumentSymbol { 1081 name: label.to_string(), 1082 detail: Some( 1083 Printer::new(&module.ast.names) 1084 .print_type(&argument.type_) 1085 .to_string(), 1086 ), 1087 kind: SymbolKind::FIELD, 1088 tags: None, 1089 deprecated: None, 1090 range: src_span_to_lsp_range(full_arg_span, line_numbers), 1091 selection_range: src_span_to_lsp_range(*label_location, line_numbers), 1092 children: None, 1093 }); 1094 } 1095 1096 // Start from the documentation if available, otherwise from the constructor's name, 1097 // all the way to the end of its arguments. 1098 let full_constructor_span = SrcSpan { 1099 start: constructor 1100 .documentation 1101 .as_ref() 1102 .map(|(doc_start, _)| get_doc_marker_pos(*doc_start)) 1103 .unwrap_or(constructor.location.start), 1104 1105 end: constructor.location.end, 1106 }; 1107 1108 // The 'deprecated' field is deprecated, but we have to specify it anyway 1109 // to be able to construct the 'DocumentSymbol' type, so 1110 // we suppress the warning. We specify 'None' as specifying 'Some' 1111 // is what is actually deprecated. 1112 #[allow(deprecated)] 1113 DocumentSymbol { 1114 name: constructor.name.to_string(), 1115 detail: None, 1116 kind: if constructor.arguments.is_empty() { 1117 SymbolKind::ENUM_MEMBER 1118 } else { 1119 SymbolKind::CONSTRUCTOR 1120 }, 1121 tags: make_deprecated_symbol_tag(&constructor.deprecation), 1122 deprecated: None, 1123 range: src_span_to_lsp_range(full_constructor_span, line_numbers), 1124 selection_range: src_span_to_lsp_range(constructor.name_location, line_numbers), 1125 children: if arguments.is_empty() { 1126 None 1127 } else { 1128 Some(arguments) 1129 }, 1130 } 1131 }) 1132 .collect_vec(); 1133 1134 // The type's location, by default, ranges from "(pub) type" to the end of its name. 1135 // We need it to range to the end of its constructors instead for the full symbol range. 1136 // We also include documentation, if available, by LSP convention. 1137 let full_type_span = SrcSpan { 1138 start: type_ 1139 .documentation 1140 .as_ref() 1141 .map(|(doc_start, _)| get_doc_marker_pos(*doc_start)) 1142 .unwrap_or(type_.location.start), 1143 1144 end: type_.end_position, 1145 }; 1146 1147 // The 'deprecated' field is deprecated, but we have to specify it anyway 1148 // to be able to construct the 'DocumentSymbol' type, so 1149 // we suppress the warning. We specify 'None' as specifying 'Some' 1150 // is what is actually deprecated. 1151 #[allow(deprecated)] 1152 DocumentSymbol { 1153 name: type_.name.to_string(), 1154 detail: None, 1155 kind: SymbolKind::CLASS, 1156 tags: make_deprecated_symbol_tag(&type_.deprecation), 1157 deprecated: None, 1158 range: src_span_to_lsp_range(full_type_span, line_numbers), 1159 selection_range: src_span_to_lsp_range(type_.name_location, line_numbers), 1160 children: if constructors.is_empty() { 1161 None 1162 } else { 1163 Some(constructors) 1164 }, 1165 } 1166} 1167 1168fn hover_for_pattern(pattern: &TypedPattern, line_numbers: LineNumbers, module: &Module) -> Hover { 1169 let documentation = pattern.get_documentation().unwrap_or_default(); 1170 1171 // Show the type of the hovered node to the user 1172 let type_ = Printer::new(&module.ast.names).print_type(pattern.type_().as_ref()); 1173 let contents = format!( 1174 "```gleam 1175{type_} 1176``` 1177{documentation}" 1178 ); 1179 Hover { 1180 contents: HoverContents::Scalar(MarkedString::String(contents)), 1181 range: Some(src_span_to_lsp_range(pattern.location(), &line_numbers)), 1182 } 1183} 1184 1185fn get_function_type(fun: &TypedFunction) -> Type { 1186 Type::Fn { 1187 args: fun.arguments.iter().map(|arg| arg.type_.clone()).collect(), 1188 return_: fun.return_type.clone(), 1189 } 1190} 1191 1192fn hover_for_function_head( 1193 fun: &TypedFunction, 1194 line_numbers: LineNumbers, 1195 module: &Module, 1196) -> Hover { 1197 let empty_str = EcoString::from(""); 1198 let documentation = fun 1199 .documentation 1200 .as_ref() 1201 .map(|(_, doc)| doc) 1202 .unwrap_or(&empty_str); 1203 let function_type = get_function_type(fun); 1204 let formatted_type = Printer::new(&module.ast.names).print_type(&function_type); 1205 let contents = format!( 1206 "```gleam 1207{formatted_type} 1208``` 1209{documentation}" 1210 ); 1211 Hover { 1212 contents: HoverContents::Scalar(MarkedString::String(contents)), 1213 range: Some(src_span_to_lsp_range(fun.location, &line_numbers)), 1214 } 1215} 1216 1217fn hover_for_function_argument( 1218 argument: &TypedArg, 1219 line_numbers: LineNumbers, 1220 module: &Module, 1221) -> Hover { 1222 let type_ = Printer::new(&module.ast.names).print_type(&argument.type_); 1223 let contents = format!("```gleam\n{type_}\n```"); 1224 Hover { 1225 contents: HoverContents::Scalar(MarkedString::String(contents)), 1226 range: Some(src_span_to_lsp_range(argument.location, &line_numbers)), 1227 } 1228} 1229 1230fn hover_for_annotation( 1231 location: SrcSpan, 1232 annotation_type: &Type, 1233 type_constructor: Option<&TypeConstructor>, 1234 line_numbers: LineNumbers, 1235 module: &Module, 1236) -> Hover { 1237 let empty_str = EcoString::from(""); 1238 let documentation = type_constructor 1239 .and_then(|t| t.documentation.as_ref()) 1240 .unwrap_or(&empty_str); 1241 // If a user is hovering an annotation, it's not very useful to show the 1242 // local representation of that type, since that's probably what they see 1243 // in the source code anyway. So here, we print the raw type, 1244 // which is probably more helpful. 1245 let type_ = Printer::new(&module.ast.names).print_type_without_aliases(annotation_type); 1246 let contents = format!( 1247 "```gleam 1248{type_} 1249``` 1250{documentation}" 1251 ); 1252 Hover { 1253 contents: HoverContents::Scalar(MarkedString::String(contents)), 1254 range: Some(src_span_to_lsp_range(location, &line_numbers)), 1255 } 1256} 1257 1258fn hover_for_label( 1259 location: SrcSpan, 1260 type_: Arc<Type>, 1261 line_numbers: LineNumbers, 1262 module: &Module, 1263) -> Hover { 1264 let type_ = Printer::new(&module.ast.names).print_type(&type_); 1265 let contents = format!("```gleam\n{type_}\n```"); 1266 Hover { 1267 contents: HoverContents::Scalar(MarkedString::String(contents)), 1268 range: Some(src_span_to_lsp_range(location, &line_numbers)), 1269 } 1270} 1271 1272fn hover_for_module_constant( 1273 constant: &ModuleConstant<Arc<Type>, EcoString>, 1274 line_numbers: LineNumbers, 1275 module: &Module, 1276) -> Hover { 1277 let empty_str = EcoString::from(""); 1278 let type_ = Printer::new(&module.ast.names).print_type(&constant.type_); 1279 let documentation = constant 1280 .documentation 1281 .as_ref() 1282 .map(|(_, doc)| doc) 1283 .unwrap_or(&empty_str); 1284 let contents = format!("```gleam\n{type_}\n```\n{documentation}"); 1285 Hover { 1286 contents: HoverContents::Scalar(MarkedString::String(contents)), 1287 range: Some(src_span_to_lsp_range(constant.location, &line_numbers)), 1288 } 1289} 1290 1291fn hover_for_constant( 1292 constant: &TypedConstant, 1293 line_numbers: LineNumbers, 1294 module: &Module, 1295) -> Hover { 1296 let type_ = Printer::new(&module.ast.names).print_type(&constant.type_()); 1297 let contents = format!("```gleam\n{type_}\n```"); 1298 Hover { 1299 contents: HoverContents::Scalar(MarkedString::String(contents)), 1300 range: Some(src_span_to_lsp_range(constant.location(), &line_numbers)), 1301 } 1302} 1303 1304fn hover_for_expression( 1305 expression: &TypedExpr, 1306 line_numbers: LineNumbers, 1307 module: &Module, 1308 hex_deps: &HashSet<EcoString>, 1309) -> Hover { 1310 let documentation = expression.get_documentation().unwrap_or_default(); 1311 1312 let link_section = get_expr_qualified_name(expression) 1313 .and_then(|(module_name, name)| { 1314 get_hexdocs_link_section(module_name, name, &module.ast, hex_deps) 1315 }) 1316 .unwrap_or("".to_string()); 1317 1318 // Show the type of the hovered node to the user 1319 let type_ = Printer::new(&module.ast.names).print_type(expression.type_().as_ref()); 1320 let contents = format!( 1321 "```gleam 1322{type_} 1323``` 1324{documentation}{link_section}" 1325 ); 1326 Hover { 1327 contents: HoverContents::Scalar(MarkedString::String(contents)), 1328 range: Some(src_span_to_lsp_range(expression.location(), &line_numbers)), 1329 } 1330} 1331 1332fn hover_for_imported_value( 1333 value: &ValueConstructor, 1334 location: &SrcSpan, 1335 line_numbers: LineNumbers, 1336 hex_module_imported_from: Option<&ModuleInterface>, 1337 name: &EcoString, 1338 module: &Module, 1339) -> Hover { 1340 let documentation = value.get_documentation().unwrap_or_default(); 1341 1342 let link_section = hex_module_imported_from.map_or("".to_string(), |m| { 1343 format_hexdocs_link_section(m.package.as_str(), m.name.as_str(), Some(name)) 1344 }); 1345 1346 // Show the type of the hovered node to the user 1347 let type_ = Printer::new(&module.ast.names).print_type(value.type_.as_ref()); 1348 let contents = format!( 1349 "```gleam 1350{type_} 1351``` 1352{documentation}{link_section}" 1353 ); 1354 Hover { 1355 contents: HoverContents::Scalar(MarkedString::String(contents)), 1356 range: Some(src_span_to_lsp_range(*location, &line_numbers)), 1357 } 1358} 1359 1360fn hover_for_module( 1361 module: &ModuleInterface, 1362 location: SrcSpan, 1363 line_numbers: &LineNumbers, 1364 hex_deps: &HashSet<EcoString>, 1365) -> Hover { 1366 let documentation = module.documentation.join("\n"); 1367 let name = &module.name; 1368 1369 let link_section = if hex_deps.contains(&module.package) { 1370 format_hexdocs_link_section(&module.package, name, None) 1371 } else { 1372 String::new() 1373 }; 1374 1375 let contents = format!( 1376 "```gleam 1377{name} 1378``` 1379{documentation} 1380{link_section}", 1381 ); 1382 Hover { 1383 contents: HoverContents::Scalar(MarkedString::String(contents)), 1384 range: Some(src_span_to_lsp_range(location, line_numbers)), 1385 } 1386} 1387 1388// Returns true if any part of either range overlaps with the other. 1389pub fn overlaps(a: Range, b: Range) -> bool { 1390 position_within(a.start, b) 1391 || position_within(a.end, b) 1392 || position_within(b.start, a) 1393 || position_within(b.end, a) 1394} 1395 1396// Returns true if a range is contained within another. 1397pub fn within(a: Range, b: Range) -> bool { 1398 position_within(a.start, b) && position_within(a.end, b) 1399} 1400 1401// Returns true if a position is within a range. 1402fn position_within(position: Position, range: Range) -> bool { 1403 position >= range.start && position <= range.end 1404} 1405 1406/// Builds the code action to assign an unused value to `_`. 1407/// 1408fn code_action_unused_values( 1409 module: &Module, 1410 line_numbers: &LineNumbers, 1411 params: &lsp::CodeActionParams, 1412 actions: &mut Vec<CodeAction>, 1413) { 1414 let uri = &params.text_document.uri; 1415 let mut unused_values: Vec<&SrcSpan> = module 1416 .ast 1417 .type_info 1418 .warnings 1419 .iter() 1420 .filter_map(|warning| match warning { 1421 type_::Warning::ImplicitlyDiscardedResult { location } => Some(location), 1422 _ => None, 1423 }) 1424 .collect(); 1425 1426 if unused_values.is_empty() { 1427 return; 1428 } 1429 1430 // Sort spans by start position, with longer spans coming first 1431 unused_values.sort_by_key(|span| (span.start, -(span.end as i64 - span.start as i64))); 1432 1433 let mut processed_lsp_range = Vec::new(); 1434 1435 for unused in unused_values { 1436 let SrcSpan { start, end } = *unused; 1437 let hover_range = src_span_to_lsp_range(SrcSpan::new(start, end), line_numbers); 1438 1439 // Check if this span is contained within any previously processed span 1440 if processed_lsp_range 1441 .iter() 1442 .any(|&prev_lsp_range| within(hover_range, prev_lsp_range)) 1443 { 1444 continue; 1445 } 1446 1447 // Check if the cursor is within this span 1448 if !within(params.range, hover_range) { 1449 continue; 1450 } 1451 1452 let edit = TextEdit { 1453 range: src_span_to_lsp_range(SrcSpan::new(start, start), line_numbers), 1454 new_text: "let _ = ".into(), 1455 }; 1456 1457 CodeActionBuilder::new("Assign unused Result value to `_`") 1458 .kind(lsp_types::CodeActionKind::QUICKFIX) 1459 .changes(uri.clone(), vec![edit]) 1460 .preferred(true) 1461 .push_to(actions); 1462 1463 processed_lsp_range.push(hover_range); 1464 } 1465} 1466 1467struct NameCorrection { 1468 pub location: SrcSpan, 1469 pub correction: EcoString, 1470} 1471 1472fn code_action_fix_names( 1473 line_numbers: &LineNumbers, 1474 params: &lsp::CodeActionParams, 1475 error: &Option<Error>, 1476 actions: &mut Vec<CodeAction>, 1477) { 1478 let uri = &params.text_document.uri; 1479 let Some(Error::Type { errors, .. }) = error else { 1480 return; 1481 }; 1482 let name_corrections = errors 1483 .iter() 1484 .filter_map(|error| match error { 1485 type_::Error::BadName { 1486 location, 1487 name, 1488 kind, 1489 } => Some(NameCorrection { 1490 correction: correct_name_case(name, *kind), 1491 location: *location, 1492 }), 1493 _ => None, 1494 }) 1495 .collect_vec(); 1496 1497 if name_corrections.is_empty() { 1498 return; 1499 } 1500 1501 for name_correction in name_corrections { 1502 let NameCorrection { 1503 location, 1504 correction, 1505 } = name_correction; 1506 1507 let range = src_span_to_lsp_range(location, line_numbers); 1508 // Check if the user's cursor is on the invalid name 1509 if overlaps(params.range, range) { 1510 let edit = TextEdit { 1511 range, 1512 new_text: correction.to_string(), 1513 }; 1514 1515 CodeActionBuilder::new(&format!("Rename to {correction}")) 1516 .kind(lsp_types::CodeActionKind::QUICKFIX) 1517 .changes(uri.clone(), vec![edit]) 1518 .preferred(true) 1519 .push_to(actions); 1520 } 1521 } 1522} 1523 1524fn get_expr_qualified_name(expression: &TypedExpr) -> Option<(&EcoString, &EcoString)> { 1525 match expression { 1526 TypedExpr::Var { 1527 name, constructor, .. 1528 } if constructor.publicity.is_importable() => match &constructor.variant { 1529 ValueConstructorVariant::ModuleFn { 1530 module: module_name, 1531 .. 1532 } => Some((module_name, name)), 1533 1534 ValueConstructorVariant::ModuleConstant { 1535 module: module_name, 1536 .. 1537 } => Some((module_name, name)), 1538 1539 _ => None, 1540 }, 1541 1542 TypedExpr::ModuleSelect { 1543 label, module_name, .. 1544 } => Some((module_name, label)), 1545 1546 _ => None, 1547 } 1548} 1549 1550fn format_hexdocs_link_section( 1551 package_name: &str, 1552 module_name: &str, 1553 name: Option<&str>, 1554) -> String { 1555 let link = match name { 1556 Some(name) => format!("https://hexdocs.pm/{package_name}/{module_name}.html#{name}"), 1557 None => format!("https://hexdocs.pm/{package_name}/{module_name}.html"), 1558 }; 1559 format!("\nView on [HexDocs]({link})") 1560} 1561 1562fn get_hexdocs_link_section( 1563 module_name: &str, 1564 name: &str, 1565 ast: &TypedModule, 1566 hex_deps: &HashSet<EcoString>, 1567) -> Option<String> { 1568 let package_name = ast 1569 .definitions 1570 .iter() 1571 .find_map(|definition| match definition { 1572 Definition::Import(import) 1573 if import.module == module_name && hex_deps.contains(&import.package) => 1574 { 1575 Some(&import.package) 1576 } 1577 _ => None, 1578 })?; 1579 1580 Some(format_hexdocs_link_section( 1581 package_name, 1582 module_name, 1583 Some(name), 1584 )) 1585} 1586 1587/// Converts the source start position of a documentation comment's contents into 1588/// the position of the leading slash in its marker ('///'). 1589fn get_doc_marker_pos(content_pos: u32) -> u32 { 1590 content_pos.saturating_sub(3) 1591} 1592 1593fn make_deprecated_symbol_tag(deprecation: &Deprecation) -> Option<Vec<SymbolTag>> { 1594 deprecation 1595 .is_deprecated() 1596 .then(|| vec![SymbolTag::DEPRECATED]) 1597}