Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

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