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

Configure Feed

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

gleam / language-server / src / engine.rs
90 kB 2433 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2023 The Gleam contributors 3 4use camino::Utf8PathBuf; 5use ecow::{EcoString, eco_format}; 6use gleam_core::{ 7 Error, Result, Warning, 8 analyse::name::correct_name_case, 9 ast::{ 10 self, Constant, CustomType, DefinitionLocation, ModuleConstant, PatternUnusedArguments, 11 SrcSpan, TypedArg, TypedClauseGuard, TypedConstant, TypedExpr, TypedFunction, TypedModule, 12 TypedPattern, TypedRecordConstructor, 13 }, 14 build::{ 15 ExpressionPosition, Located, Module, UnqualifiedImport, type_constructor_from_modules, 16 }, 17 config::PackageConfig, 18 io::{BeamCompilerIO, CommandExecutor, FileSystemReader, FileSystemWriter}, 19 line_numbers::LineNumbers, 20 paths::ProjectPaths, 21 type_::{ 22 self, Deprecation, ModuleInterface, Type, TypeConstructor, ValueConstructor, 23 ValueConstructorVariant, 24 error::{Named, VariableSyntax}, 25 printer::Printer, 26 }, 27}; 28use itertools::Itertools; 29use lsp::CodeAction; 30use lsp_server::ResponseError; 31use lsp_types::{ 32 self as lsp, Contents, DocumentSymbol, FoldingRange, FoldingRangeKind, Hover, MarkedString, 33 MarkupContent, Position, PrepareRenameResult, Range, SignatureHelp, SymbolKind, SymbolTag, 34 TextEdit, Uri as Url, WorkspaceEdit, 35}; 36use std::{ 37 collections::{HashMap, HashSet}, 38 sync::Arc, 39}; 40 41use crate::{ 42 code_action::{ 43 DiscardUnusedVariable, RemoveRedundantRecordUpdate, ReplaceUnderscoreWithType, 44 code_action_fix_deprecated_pipe, type_errors_for_module, 45 }, 46 reference::find_module_references_in_module, 47 rename::{rename_module_alias, rename_module_occurrences, rename_type_variable}, 48}; 49 50use super::{ 51 DownloadDependencies, MakeLocker, 52 code_action::{ 53 AddAnnotations, AddMissingTypeParameter, AddOmittedLabels, AnnotateTopLevelDefinitions, 54 CodeActionBuilder, CollapseNestedCase, ConvertFromUse, ConvertToFunctionCall, 55 ConvertToPipe, ConvertToUse, CreateUnknownModule, ExpandFunctionCapture, ExtractConstant, 56 ExtractFunction, ExtractVariable, FillInMissingLabelledArgs, FillUnusedFields, 57 FixBinaryOperation, FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, 58 GenerateJsonEncoder, GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, 59 MergeCaseBranches, PatternMatchOnValue, RedundantTupleInCaseSubject, RemoveBlock, 60 RemoveEchos, RemovePrivateOpaque, RemoveUnreachableCaseClauses, RemoveUnusedImports, 61 UnwrapAnonymousFunction, UseLabelShorthandSyntax, WrapInAnonymousFunction, WrapInBlock, 62 code_action_add_missing_patterns, code_action_convert_qualified_constructor_to_unqualified, 63 code_action_convert_unqualified_constructor_to_qualified, code_action_generate_type, 64 code_action_import_module, code_action_inexhaustive_let_to_case, 65 }, 66 compiler::LspProjectCompiler, 67 completer::Completer, 68 files::FileSystemProxy, 69 progress::ProgressReporter, 70 reference::{ 71 FindVariableReferences, Referenced, VariableReferenceKind, find_label_references, 72 find_label_references_in_module, find_module_references, reference_for_ast_node, 73 }, 74 rename::{ 75 RenameOutcome, RenameTarget, Renamed, rename_label, rename_local_variable, 76 rename_module_entity, 77 }, 78 signature_help, src_span_to_lsp_range, 79}; 80 81#[derive(Debug, PartialEq, Eq)] 82pub struct Response<T> { 83 pub result: Result<T, Error>, 84 pub warnings: Vec<Warning>, 85 pub compilation: Compilation, 86} 87 88#[derive(Debug, PartialEq, Eq)] 89pub enum Compilation { 90 /// Compilation was attempted and succeeded for these modules. 91 Yes(Vec<Utf8PathBuf>), 92 /// Compilation was not attempted for this operation. 93 No, 94} 95 96#[derive(Debug)] 97pub struct LanguageServerEngine<IO, Reporter> { 98 pub(crate) paths: ProjectPaths, 99 100 /// A compiler for the project that supports repeat compilation of the root 101 /// package. 102 /// In the event the project config changes this will need to be 103 /// discarded and reloaded to handle any changes to dependencies. 104 pub(crate) compiler: LspProjectCompiler<FileSystemProxy<IO>>, 105 106 modules_compiled_since_last_feedback: Vec<Utf8PathBuf>, 107 compiled_since_last_feedback: bool, 108 error: Option<Error>, 109 110 // Used to publish progress notifications to the client without waiting for 111 // the usual request-response loop. 112 progress_reporter: Reporter, 113 114 /// Used to know if to show the "View on HexDocs" link 115 /// when hovering on an imported value 116 hex_deps: HashSet<EcoString>, 117} 118 119impl<'a, IO, Reporter> LanguageServerEngine<IO, Reporter> 120where 121 // IO to be supplied from outside of gleam-core 122 IO: FileSystemReader 123 + FileSystemWriter 124 + BeamCompilerIO 125 + CommandExecutor 126 + DownloadDependencies 127 + MakeLocker 128 + Clone, 129 // IO to be supplied from inside of gleam-core 130 Reporter: ProgressReporter + Clone + 'a, 131{ 132 pub fn new( 133 config: PackageConfig, 134 progress_reporter: Reporter, 135 io: FileSystemProxy<IO>, 136 paths: ProjectPaths, 137 ) -> Result<Self> { 138 let locker = io.inner().make_locker(&paths, config.target)?; 139 140 // Download dependencies to ensure they are up-to-date for this new 141 // configuration and new instance of the compiler 142 progress_reporter.dependency_downloading_started(); 143 let manifest = io.inner().download_dependencies(&paths); 144 progress_reporter.dependency_downloading_finished(); 145 146 // NOTE: This must come after the progress reporter has finished! 147 let manifest = manifest?; 148 149 let compiler: LspProjectCompiler<FileSystemProxy<IO>> = 150 LspProjectCompiler::new(manifest, config, paths.clone(), io.clone(), locker)?; 151 152 let hex_deps = compiler 153 .project_compiler 154 .packages 155 .iter() 156 .flat_map(|(k, v)| match &v.source { 157 gleam_core::manifest::ManifestPackageSource::Hex { .. } => { 158 Some(EcoString::from(k.as_str())) 159 } 160 161 gleam_core::manifest::ManifestPackageSource::Git { .. } 162 | gleam_core::manifest::ManifestPackageSource::Local { .. } => None, 163 }) 164 .collect(); 165 166 Ok(Self { 167 modules_compiled_since_last_feedback: vec![], 168 compiled_since_last_feedback: false, 169 progress_reporter, 170 compiler, 171 paths, 172 error: None, 173 hex_deps, 174 }) 175 } 176 177 pub fn compile_please(&mut self) -> Response<()> { 178 self.respond(Self::compile) 179 } 180 181 /// Compile the project if we are in one. Otherwise do nothing. 182 fn compile(&mut self) -> Result<(), Error> { 183 self.compiled_since_last_feedback = true; 184 185 self.progress_reporter.compilation_started(); 186 let outcome = self.compiler.compile(); 187 self.progress_reporter.compilation_finished(); 188 189 let result = outcome 190 // Register which modules have changed 191 .map(|modules| self.modules_compiled_since_last_feedback.extend(modules)) 192 // Return the error, if present 193 .into_result(); 194 195 self.error = match &result { 196 Ok(_) => None, 197 Err(error) => Some(error.clone()), 198 }; 199 200 result 201 } 202 203 fn take_warnings(&mut self) -> Vec<Warning> { 204 self.compiler.take_warnings() 205 } 206 207 pub fn goto_definition( 208 &mut self, 209 params: lsp::DefinitionParams, 210 ) -> Response<Option<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(None), 216 }; 217 218 let Some(location) = 219 node.definition_location(this.compiler.project_compiler.get_importable_modules()) 220 else { 221 return Ok(None); 222 }; 223 224 Ok(this.definition_location_to_lsp_location(&line_numbers, &params, location)) 225 }) 226 } 227 228 pub(crate) fn goto_type_definition( 229 &mut self, 230 params: lsp_types::TypeDefinitionParams, 231 ) -> Response<Vec<lsp::Location>> { 232 self.respond(|this| { 233 let params = params.text_document_position_params; 234 let (line_numbers, node) = match this.node_at_position(&params) { 235 Some(location) => location, 236 None => return Ok(vec![]), 237 }; 238 239 let Some(locations) = node 240 .type_definition_locations(this.compiler.project_compiler.get_importable_modules()) 241 else { 242 return Ok(vec![]); 243 }; 244 245 let locations = locations 246 .into_iter() 247 .filter_map(|location| { 248 this.definition_location_to_lsp_location(&line_numbers, &params, location) 249 }) 250 .collect_vec(); 251 252 Ok(locations) 253 }) 254 } 255 256 fn definition_location_to_lsp_location( 257 &self, 258 line_numbers: &LineNumbers, 259 params: &lsp_types::TextDocumentPositionParams, 260 location: DefinitionLocation, 261 ) -> Option<lsp::Location> { 262 let (uri, line_numbers) = match location.module { 263 None => (params.text_document.uri.clone(), line_numbers), 264 Some(name) => { 265 let module = self.compiler.get_source(&name)?; 266 let url = Url::parse(&format!("file:///{}", &module.path)) 267 .expect("goto definition URL parse"); 268 (url, &module.line_numbers) 269 } 270 }; 271 let range = src_span_to_lsp_range(location.span, line_numbers); 272 273 Some(lsp::Location { uri, range }) 274 } 275 276 pub fn completion( 277 &mut self, 278 params: lsp::TextDocumentPositionParams, 279 src: EcoString, 280 ) -> Response<Option<Vec<lsp::CompletionItem>>> { 281 self.respond(|this| { 282 let module = match this.module_for_uri(&params.text_document.uri) { 283 Some(module) => module, 284 None => return Ok(None), 285 }; 286 287 let mut completer = Completer::new(&src, &params, &this.compiler, module); 288 let byte_index = completer.module_line_numbers.byte_index(params.position); 289 290 // If in comment context, do not provide completions 291 if module.extra.is_within_comment(byte_index) { 292 return Ok(None); 293 } 294 295 // Check current file contents if the user is writing an import 296 // and handle separately from the rest of the completion flow 297 // Check if an import is being written 298 if let Some(value) = completer.import_completions() { 299 return value; 300 } 301 302 let Some(found) = module.find_node(byte_index) else { 303 return Ok(None); 304 }; 305 306 let completions = match found { 307 Located::PatternSpread { .. } => None, 308 Located::Pattern(_pattern) => None, 309 Located::StringPrefixPatternVariable { .. } => None, 310 311 // Do not show completions when typing inside a string. 312 Located::Expression { 313 expression: TypedExpr::String { .. }, 314 .. 315 } 316 | Located::Constant(Constant::String { .. }) => None, 317 318 Located::Expression { 319 expression: 320 TypedExpr::Call { fun, arguments, .. } 321 | TypedExpr::RecordUpdate { 322 constructor: fun, 323 arguments, 324 .. 325 }, 326 .. 327 } => { 328 let mut completions = vec![]; 329 completions.append(&mut completer.completion_values()); 330 completions.append(&mut completer.completion_labels(fun, arguments)); 331 Some(completions) 332 } 333 334 Located::Expression { 335 expression: TypedExpr::RecordAccess { record, type_, .. }, 336 .. 337 } => { 338 completer.expected_type = Some(type_.clone()); 339 let mut completions = vec![]; 340 completions.append(&mut completer.completion_values()); 341 completions.append(&mut completer.completion_field_accessors(record.type_())); 342 Some(completions) 343 } 344 345 Located::Expression { 346 position: 347 ExpressionPosition::ArgumentOrLabel { 348 called_function, 349 function_arguments, 350 }, 351 .. 352 } => { 353 let mut completions = vec![]; 354 completions.append(&mut completer.completion_values()); 355 completions.append( 356 &mut completer.completion_labels(called_function, function_arguments), 357 ); 358 Some(completions) 359 } 360 361 // If we're typing inside an expression body (and not being any 362 // more specific than this, meaning we're not editing some 363 // specific value inside it) we don't want to set the expected 364 // type to the type of the entire anonymous function, otherwise 365 // the language server would start recommending the wrong 366 // values: 367 // 368 // ``` 369 // fn(x: Int) -> String { 370 // | // <- Typing here 371 // // We don't want the language server to suggest values 372 // // of type `fn(Int) -> String`! 373 // todo 374 // } 375 // ``` 376 // 377 Located::Expression { 378 position: ExpressionPosition::Expression, 379 expression: TypedExpr::Fn { .. }, 380 } => Some(completer.completion_values()), 381 382 Located::Expression { expression, .. } => { 383 completer.expected_type = Some(expression.type_()); 384 Some(completer.completion_values()) 385 } 386 387 Located::ModuleFunction(_) => Some(completer.completion_types()), 388 389 Located::Statement(_) => Some(completer.completion_values()), 390 391 Located::FunctionBody(_) => Some(completer.completion_values()), 392 393 Located::ModuleTypeAlias(_) 394 | Located::ModuleCustomType(_) 395 | Located::VariantConstructorDefinition(_) => Some(completer.completion_types()), 396 397 // If the import completions returned no results and we are in an import then 398 // we should try to provide completions for unqualified values 399 Located::ModuleImport(import) => this 400 .compiler 401 .get_module_interface(import.module.as_str()) 402 .map(|importing_module| { 403 completer.unqualified_completions_from_module(importing_module, true) 404 }), 405 406 Located::ModuleConstant(_) | Located::Constant(_) => { 407 Some(completer.completion_values()) 408 } 409 410 Located::UnqualifiedImport(_) => None, 411 412 Located::Arg(_) => None, 413 414 Located::Annotation { .. } => Some(completer.completion_types()), 415 416 Located::Label { .. } 417 | Located::RecordLabelDefinition { .. } 418 | Located::RecordLabelUsage { .. } => None, 419 420 Located::RecordAccessLabel { 421 field_type, 422 record_type, 423 .. 424 } => { 425 completer.expected_type = Some(field_type); 426 let mut completions = vec![]; 427 completions.append(&mut completer.completion_values()); 428 completions.append(&mut completer.completion_field_accessors(record_type)); 429 Some(completions) 430 } 431 432 Located::ModuleName { 433 layer: ast::Layer::Type, 434 .. 435 } => Some(completer.completion_types()), 436 Located::ModuleName { 437 layer: ast::Layer::Value, 438 .. 439 } => Some(completer.completion_values()), 440 441 Located::ClauseGuard(_) => Some(completer.completion_values()), 442 443 Located::TypeVariable { .. } => None, 444 }; 445 446 Ok(completions) 447 }) 448 } 449 450 pub fn code_actions( 451 &mut self, 452 params: lsp::CodeActionParams, 453 ) -> Response<Option<Vec<CodeAction>>> { 454 self.respond(|this| { 455 let mut actions = vec![]; 456 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 457 return Ok(None); 458 }; 459 460 let lines = LineNumbers::new(&module.code); 461 462 code_action_unused_values(module, &lines, &params, &mut actions); 463 actions.extend(RemoveUnusedImports::new(module, &lines, &params).code_actions()); 464 code_action_fix_names(module, &lines, &params, &this.error, &mut actions); 465 code_action_import_module(module, &lines, &params, &this.error, &mut actions); 466 code_action_generate_type(module, &lines, &params, &this.error, &mut actions); 467 code_action_add_missing_patterns(module, &lines, &params, &this.error, &mut actions); 468 actions 469 .extend(RemoveUnreachableCaseClauses::new(module, &lines, &params).code_actions()); 470 actions 471 .extend(RemoveRedundantRecordUpdate::new(module, &lines, &params).code_actions()); 472 actions.extend(CollapseNestedCase::new(module, &lines, &params).code_actions()); 473 actions.extend(FixBinaryOperation::new(module, &lines, &params).code_actions()); 474 actions 475 .extend(FixTruncatedBitArraySegment::new(module, &lines, &params).code_actions()); 476 actions.extend(RemovePrivateOpaque::new(module, &lines, &params).code_actions()); 477 actions.extend(AddMissingTypeParameter::new(module, &lines, &params).code_actions()); 478 code_action_convert_qualified_constructor_to_unqualified( 479 module, 480 &this.compiler, 481 &lines, 482 &params, 483 &mut actions, 484 ); 485 code_action_convert_unqualified_constructor_to_qualified( 486 module, 487 &lines, 488 &params, 489 &mut actions, 490 ); 491 code_action_inexhaustive_let_to_case( 492 module, 493 &lines, 494 &params, 495 &this.error, 496 &mut actions, 497 ); 498 actions.extend(MergeCaseBranches::new(module, &lines, &params).code_actions()); 499 actions.extend(LetAssertToCase::new(module, &lines, &params).code_actions()); 500 actions 501 .extend(RedundantTupleInCaseSubject::new(module, &lines, &params).code_actions()); 502 actions.extend(FillInMissingLabelledArgs::new(module, &lines, &params).code_actions()); 503 actions.extend(UseLabelShorthandSyntax::new(module, &lines, &params).code_actions()); 504 actions.extend(ConvertFromUse::new(module, &lines, &params).code_actions()); 505 actions.extend(RemoveEchos::new(module, &lines, &params).code_actions()); 506 actions.extend(ConvertToUse::new(module, &lines, &params).code_actions()); 507 actions.extend(ExpandFunctionCapture::new(module, &lines, &params).code_actions()); 508 actions.extend(FillUnusedFields::new(module, &lines, &params).code_actions()); 509 actions.extend(InterpolateString::new(module, &lines, &params).code_actions()); 510 actions.extend(ExtractVariable::new(module, &lines, &params).code_actions()); 511 actions.extend(ExtractConstant::new(module, &lines, &params).code_actions()); 512 actions.extend( 513 GenerateFunction::new(module, &this.compiler.modules, &lines, &params) 514 .code_actions(), 515 ); 516 actions.extend( 517 GenerateVariant::new(module, &this.compiler, &lines, &params).code_actions(), 518 ); 519 actions.extend(ConvertToPipe::new(module, &lines, &params).code_actions()); 520 actions.extend(ConvertToFunctionCall::new(module, &lines, &params).code_actions()); 521 actions.extend( 522 PatternMatchOnValue::new(module, &lines, &params, &this.compiler).code_actions(), 523 ); 524 actions.extend(AddOmittedLabels::new(module, &lines, &params).code_actions()); 525 actions.extend(InlineVariable::new(module, &lines, &params).code_actions()); 526 actions.extend(WrapInBlock::new(module, &lines, &params).code_actions()); 527 actions.extend(RemoveBlock::new(module, &lines, &params).code_actions()); 528 actions.extend(ExtractFunction::new(module, &lines, &params).code_actions()); 529 GenerateDynamicDecoder::new(module, &lines, &params, &mut actions, &this.compiler) 530 .code_actions(); 531 actions.extend(WrapInAnonymousFunction::new(module, &lines, &params).code_actions()); 532 actions.extend(UnwrapAnonymousFunction::new(module, &lines, &params).code_actions()); 533 GenerateJsonEncoder::new( 534 module, 535 &lines, 536 &params, 537 &mut actions, 538 &this.compiler.project_compiler.config, 539 ) 540 .code_actions(); 541 AddAnnotations::new(module, &lines, &params).code_action(&mut actions); 542 actions 543 .extend(AnnotateTopLevelDefinitions::new(module, &lines, &params).code_actions()); 544 actions.extend(ReplaceUnderscoreWithType::new(module, &lines, &params).code_actions()); 545 actions.extend( 546 CreateUnknownModule::new( 547 module, 548 &this.compiler, 549 &lines, 550 &params, 551 &this.paths, 552 &this.error, 553 ) 554 .code_actions(), 555 ); 556 actions.extend(DiscardUnusedVariable::new(module, &lines, &params).code_actions()); 557 code_action_fix_deprecated_pipe(module, &lines, &params, &mut actions); 558 559 actions.sort_by_key(|one| { 560 let preferred_key = if one.is_preferred == Some(true) { 0 } else { 1 }; 561 let kind_key = match &one.kind { 562 Some(lsp_types::CodeActionKind::QuickFix) => 1, 563 Some(lsp_types::CodeActionKind::Refactor) => 2, 564 Some(lsp_types::CodeActionKind::RefactorExtract) => 2, 565 Some(lsp_types::CodeActionKind::RefactorInline) => 2, 566 Some(lsp_types::CodeActionKind::RefactorMove) => 2, 567 Some(lsp_types::CodeActionKind::RefactorRewrite) => 2, 568 Some(lsp_types::CodeActionKind::Source) => 3, 569 Some(lsp_types::CodeActionKind::SourceOrganizeImports) => 3, 570 Some(lsp_types::CodeActionKind::SourceFixAll) => 3, 571 Some(lsp_types::CodeActionKind::Custom(_)) => 4, 572 Some(lsp_types::CodeActionKind::Notebook) => 5, 573 Some(lsp_types::CodeActionKind::Empty) => 6, 574 None => 7, 575 }; 576 (preferred_key, kind_key) 577 }); 578 579 Ok(if actions.is_empty() { 580 None 581 } else { 582 Some(actions) 583 }) 584 }) 585 } 586 587 pub fn document_symbol( 588 &mut self, 589 params: lsp::DocumentSymbolParams, 590 ) -> Response<Vec<DocumentSymbol>> { 591 self.respond(|this| { 592 let mut symbols = vec![]; 593 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 594 return Ok(symbols); 595 }; 596 let line_numbers = LineNumbers::new(&module.code); 597 598 for function in &module.ast.definitions.functions { 599 // By default, the function's location ends right after the return type. 600 // For the full symbol range, have it end at the end of the body. 601 // Also include the documentation, if available. 602 // 603 // By convention, the symbol span starts from the leading slash in the 604 // documentation comment's marker ('///'), not from its content (of which 605 // we have the position), so we must convert the content start position 606 // to the leading slash's position. 607 let full_function_span = SrcSpan { 608 start: function 609 .documentation 610 .as_ref() 611 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 612 .unwrap_or(function.location.start), 613 614 end: function.end_position, 615 }; 616 617 let (name_location, name) = function 618 .name 619 .as_ref() 620 .expect("Function in a definition must be named"); 621 622 // The 'deprecated' field is deprecated, but we have to specify it anyway 623 // to be able to construct the 'DocumentSymbol' type, so 624 // we suppress the warning. We specify 'None' as specifying 'Some' 625 // is what is actually deprecated. 626 #[allow(deprecated)] 627 symbols.push(DocumentSymbol { 628 name: name.to_string(), 629 detail: Some( 630 Printer::new(&module.ast.names) 631 .print_type(&get_function_type(function)) 632 .to_string(), 633 ), 634 kind: SymbolKind::Function, 635 tags: make_deprecated_symbol_tag(&function.deprecation), 636 deprecated: None, 637 range: src_span_to_lsp_range(full_function_span, &line_numbers), 638 selection_range: src_span_to_lsp_range(*name_location, &line_numbers), 639 children: None, 640 }); 641 } 642 643 for alias in &module.ast.definitions.type_aliases { 644 let full_alias_span = match alias.documentation { 645 Some((doc_position, _)) => { 646 SrcSpan::new(get_doc_marker_position(doc_position), alias.location.end) 647 } 648 None => alias.location, 649 }; 650 651 // The 'deprecated' field is deprecated, but we have to specify it anyway 652 // to be able to construct the 'DocumentSymbol' type, so 653 // we suppress the warning. We specify 'None' as specifying 'Some' 654 // is what is actually deprecated. 655 #[allow(deprecated)] 656 symbols.push(DocumentSymbol { 657 name: alias.alias.to_string(), 658 detail: Some( 659 Printer::new(&module.ast.names) 660 // If we print with aliases, we end up printing the alias which the user 661 // is currently hovering, which is not helpful. Instead, we print the 662 // raw type, so the user can see which type the alias represents 663 .print_type_without_aliases(&alias.type_) 664 .to_string(), 665 ), 666 kind: SymbolKind::Class, 667 tags: make_deprecated_symbol_tag(&alias.deprecation), 668 deprecated: None, 669 range: src_span_to_lsp_range(full_alias_span, &line_numbers), 670 selection_range: src_span_to_lsp_range(alias.name_location, &line_numbers), 671 children: None, 672 }); 673 } 674 675 for custom_type in &module.ast.definitions.custom_types { 676 symbols.push(custom_type_symbol(custom_type, &line_numbers, module)); 677 } 678 679 for constant in &module.ast.definitions.constants { 680 // `ModuleConstant.location` ends at the constant's name or type. 681 // For the full symbol span, necessary for `range`, we need to 682 // include the constant value as well. 683 // Also include the documentation at the start, if available. 684 let full_constant_span = SrcSpan { 685 start: constant 686 .documentation 687 .as_ref() 688 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 689 .unwrap_or(constant.location.start), 690 691 end: constant.value.location().end, 692 }; 693 694 // The 'deprecated' field is deprecated, but we have to specify it anyway 695 // to be able to construct the 'DocumentSymbol' type, so 696 // we suppress the warning. We specify 'None' as specifying 'Some' 697 // is what is actually deprecated. 698 #[allow(deprecated)] 699 symbols.push(DocumentSymbol { 700 name: constant.name.to_string(), 701 detail: Some( 702 Printer::new(&module.ast.names) 703 .print_type(&constant.type_) 704 .to_string(), 705 ), 706 kind: SymbolKind::Constant, 707 tags: make_deprecated_symbol_tag(&constant.deprecation), 708 deprecated: None, 709 range: src_span_to_lsp_range(full_constant_span, &line_numbers), 710 selection_range: src_span_to_lsp_range(constant.name_location, &line_numbers), 711 children: None, 712 }); 713 } 714 715 Ok(symbols) 716 }) 717 } 718 719 pub fn folding_range( 720 &mut self, 721 params: lsp::FoldingRangeParams, 722 ) -> Response<Vec<FoldingRange>> { 723 self.respond(|this| { 724 let mut ranges: Vec<FoldingRange> = vec![]; 725 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 726 return Ok(vec![]); 727 }; 728 729 let line_numbers = LineNumbers::new(&module.code); 730 731 for import in 732 import_folding_spans(&module.ast.definitions.imports, &module.code, &line_numbers) 733 { 734 let Some(range) = 735 folding_range_for_span(import, &line_numbers, Some(FoldingRangeKind::Imports)) 736 else { 737 continue; 738 }; 739 740 ranges.push(range); 741 } 742 743 for type_ in &module.ast.definitions.custom_types { 744 let span = type_.full_location(); 745 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 746 continue; 747 }; 748 ranges.push(range); 749 } 750 751 for constant in &module.ast.definitions.constants { 752 let span = SrcSpan::new(constant.location.start, constant.value.location().end); 753 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 754 continue; 755 }; 756 ranges.push(range); 757 } 758 759 for alias in &module.ast.definitions.type_aliases { 760 let span = alias.location; 761 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 762 continue; 763 }; 764 ranges.push(range); 765 } 766 767 for function in &module.ast.definitions.functions { 768 let Some(body_start) = function.body_start else { 769 continue; 770 }; 771 772 let span = SrcSpan::new(body_start, function.end_position); 773 let Some(range) = folding_range_for_span(span, &line_numbers, None) else { 774 continue; 775 }; 776 ranges.push(range); 777 } 778 779 for doc_comment in comment_folding_spans( 780 &module.extra.doc_comments, 781 &module.code, 782 &line_numbers, 783 "///", 784 ) { 785 let Some(range) = folding_range_for_span( 786 doc_comment, 787 &line_numbers, 788 Some(FoldingRangeKind::Comment), 789 ) else { 790 continue; 791 }; 792 ranges.push(range); 793 } 794 795 for module_comment in comment_folding_spans( 796 &module.extra.module_comments, 797 &module.code, 798 &line_numbers, 799 "////", 800 ) { 801 let Some(range) = folding_range_for_span( 802 module_comment, 803 &line_numbers, 804 Some(FoldingRangeKind::Comment), 805 ) else { 806 continue; 807 }; 808 ranges.push(range); 809 } 810 811 for comment in 812 comment_folding_spans(&module.extra.comments, &module.code, &line_numbers, "//") 813 { 814 let Some(range) = 815 folding_range_for_span(comment, &line_numbers, Some(FoldingRangeKind::Comment)) 816 else { 817 continue; 818 }; 819 ranges.push(range); 820 } 821 822 ranges.sort_by_key(|range| range.start_line); 823 Ok(ranges) 824 }) 825 } 826 827 /// Check whether a particular module is in the same package as this one 828 fn is_same_package(&self, current_module: &Module, module_name: &str) -> bool { 829 let other_module = self 830 .compiler 831 .project_compiler 832 .get_importable_modules() 833 .get(module_name); 834 match other_module { 835 // We can't rename values from other packages if we are not aliasing an unqualified import. 836 Some(module) => module.package == current_module.ast.type_info.package, 837 None => false, 838 } 839 } 840 841 pub fn prepare_rename( 842 &mut self, 843 params: lsp::PrepareRenameParams, 844 ) -> Response<Option<PrepareRenameResult>> { 845 self.respond(|this| { 846 let (lines, found) = match this.node_at_position(&params.text_document_position_params) 847 { 848 Some(value) => value, 849 None => return Ok(None), 850 }; 851 852 let Some(current_module) = 853 this.module_for_uri(&params.text_document_position_params.text_document.uri) 854 else { 855 return Ok(None); 856 }; 857 858 let success_response = |location| { 859 Some(PrepareRenameResult::Range(src_span_to_lsp_range( 860 location, &lines, 861 ))) 862 }; 863 864 let byte_index = lines.byte_index(params.text_document_position_params.position); 865 866 let referenced = reference_for_ast_node(found, &current_module.name); 867 868 Ok(match referenced { 869 Some(Referenced::LocalVariable { 870 location, origin, .. 871 }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { 872 Some(VariableSyntax::Generated) => None, 873 Some( 874 VariableSyntax::Variable(label) | VariableSyntax::LabelShorthand(label), 875 ) => success_response(SrcSpan { 876 start: location.start, 877 end: label 878 .len() 879 .try_into() 880 .map(|len: u32| location.start + len) 881 .unwrap_or(location.end), 882 }), 883 Some(VariableSyntax::AssignmentPattern(..)) | None => { 884 success_response(location) 885 } 886 }, 887 Some( 888 Referenced::ModuleValue { 889 module, 890 location, 891 name_start, 892 target_kind, 893 .. 894 } 895 | Referenced::ModuleType { 896 module, 897 location, 898 name_start, 899 target_kind, 900 .. 901 }, 902 ) if location.contains(byte_index) => { 903 // We can't rename types or values from other packages if we are not aliasing an unqualified import. 904 let rename_allowed = match target_kind { 905 RenameTarget::Qualified => this.is_same_package(current_module, &module), 906 RenameTarget::Unqualified | RenameTarget::Definition => true, 907 }; 908 if rename_allowed { 909 // The location field is sometimes larger than the 910 // location of the actual name. In most cases they are 911 // the same, but in import statements that are for types 912 // and/or are aliased, the location is larger than the 913 // name. 914 // 915 // For example, in `import m.{type Wibble as Wobble}`, 916 // location will cover `type Wibble as Wobble` but 917 // the name is just `Wobble`. In every case (including 918 // non-imports), the name is at the very end of the 919 // location span. 920 let location = SrcSpan { 921 start: name_start, 922 end: location.end, 923 }; 924 success_response(location) 925 } else { 926 None 927 } 928 } 929 Some(Referenced::ModuleName { 930 location, 931 module_alias, 932 .. 933 }) => success_response(SrcSpan::new( 934 // Since the location contains the full module name (e.g. `wibble/wobble/woo`), 935 // we just want to include the last segment so we get a rename of the string 936 // `woo`, as that's the being referenced in module access expressions. 937 location.end - module_alias.len() as u32, 938 location.end, 939 )), 940 941 Some(Referenced::TypeVariable { location, name: _ }) => success_response(location), 942 943 // For a label written using the shorthand syntax the location 944 // spans the whole `label:`, so we trim it down to just the 945 // label. 946 Some(Referenced::Label { 947 location, 948 label, 949 type_module, 950 .. 951 }) if this.is_same_package(current_module, &type_module) => { 952 success_response(SrcSpan { 953 start: location.start, 954 end: location.start + label.len() as u32, 955 }) 956 } 957 958 _ => None, 959 }) 960 }) 961 } 962 963 pub fn rename( 964 &mut self, 965 params: lsp::RenameParams, 966 ) -> Response<Result<Option<WorkspaceEdit>, ResponseError>> { 967 self.respond(|this| { 968 let position = &params.text_document_position_params; 969 970 let (lines, found) = match this.node_at_position(position) { 971 Some(value) => value, 972 None => return Ok(RenameOutcome::NoRenames.into_result()), 973 }; 974 975 let Some(module) = this.module_for_uri(&position.text_document.uri) else { 976 return Ok(RenameOutcome::NoRenames.into_result()); 977 }; 978 979 let referenced = reference_for_ast_node(found, &module.name); 980 981 Ok(match referenced { 982 Some(Referenced::LocalVariable { 983 origin, 984 definition_location, 985 name, 986 .. 987 }) => { 988 let rename_kind = match origin.map(|origin| origin.syntax) { 989 Some(VariableSyntax::Generated) => { 990 return Ok(RenameOutcome::NoRenames.into_result()); 991 } 992 Some(VariableSyntax::LabelShorthand(_)) => { 993 VariableReferenceKind::LabelShorthand 994 } 995 Some( 996 VariableSyntax::AssignmentPattern(..) | VariableSyntax::Variable { .. }, 997 ) 998 | None => VariableReferenceKind::Variable, 999 }; 1000 rename_local_variable( 1001 module, 1002 &lines, 1003 &params, 1004 definition_location, 1005 name, 1006 rename_kind, 1007 ) 1008 .into_result() 1009 } 1010 Some(Referenced::ModuleValue { 1011 module: module_name, 1012 target_kind, 1013 name, 1014 name_kind, 1015 .. 1016 }) => rename_module_entity( 1017 &params, 1018 module, 1019 this.compiler.project_compiler.get_importable_modules(), 1020 &this.compiler.sources, 1021 Renamed { 1022 module_name: &module_name, 1023 name: &name, 1024 name_kind, 1025 target_kind, 1026 layer: ast::Layer::Value, 1027 }, 1028 ) 1029 .into_result(), 1030 1031 Some(Referenced::ModuleType { 1032 module: module_name, 1033 target_kind, 1034 name, 1035 .. 1036 }) => rename_module_entity( 1037 &params, 1038 module, 1039 this.compiler.project_compiler.get_importable_modules(), 1040 &this.compiler.sources, 1041 Renamed { 1042 module_name: &module_name, 1043 name: &name, 1044 name_kind: Named::Type, 1045 target_kind, 1046 layer: ast::Layer::Type, 1047 }, 1048 ) 1049 .into_result(), 1050 1051 Some(Referenced::ModuleName { module_name, .. }) => { 1052 rename_module_alias(module, &lines, &params, &module_name).into_result() 1053 } 1054 1055 Some(Referenced::TypeVariable { location, name }) => { 1056 rename_type_variable(module, &lines, &params, location, name).into_result() 1057 } 1058 1059 Some(Referenced::Label { 1060 type_module, 1061 type_name, 1062 label, 1063 .. 1064 }) => rename_label( 1065 &params, 1066 &type_module, 1067 &type_name, 1068 &label, 1069 this.compiler.project_compiler.get_importable_modules(), 1070 &this.compiler.sources, 1071 ) 1072 .into_result(), 1073 1074 None => RenameOutcome::NoRenames.into_result(), 1075 }) 1076 }) 1077 } 1078 1079 /// Shared core of find references and document highlight. 1080 fn find_references_in_scope( 1081 &mut self, 1082 position: &lsp_types::TextDocumentPositionParams, 1083 search_scope: FindReferencesSearchScope, 1084 ) -> Option<Vec<lsp::Location>> { 1085 let (lines, found) = self.node_at_position(position)?; 1086 1087 let uri = position.text_document.uri.clone(); 1088 1089 let source_module = self.module_for_uri(&uri)?; 1090 1091 let byte_index = lines.byte_index(position.position); 1092 1093 let referenced = reference_for_ast_node(found, &source_module.name); 1094 1095 match referenced { 1096 Some(Referenced::LocalVariable { 1097 origin, 1098 definition_location, 1099 location, 1100 name, 1101 }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { 1102 Some(VariableSyntax::Generated) => None, 1103 Some( 1104 VariableSyntax::LabelShorthand(_) 1105 | VariableSyntax::AssignmentPattern(..) 1106 | VariableSyntax::Variable { .. }, 1107 ) 1108 | None => { 1109 let variable_references = 1110 FindVariableReferences::new(definition_location, name) 1111 .find_in_module(&source_module.ast); 1112 1113 let mut reference_locations = Vec::with_capacity(variable_references.len() + 1); 1114 reference_locations.push(lsp::Location { 1115 uri: uri.clone(), 1116 range: src_span_to_lsp_range(definition_location, &lines), 1117 }); 1118 1119 for reference in variable_references { 1120 reference_locations.push(lsp::Location { 1121 uri: uri.clone(), 1122 range: src_span_to_lsp_range(reference.location, &lines), 1123 }) 1124 } 1125 1126 Some(reference_locations) 1127 } 1128 }, 1129 Some(Referenced::ModuleValue { 1130 module, 1131 name, 1132 location, 1133 .. 1134 }) if location.contains(byte_index) => match search_scope { 1135 FindReferencesSearchScope::AllModules => Some(find_module_references( 1136 module, 1137 name, 1138 self.compiler.project_compiler.get_importable_modules(), 1139 &self.compiler.sources, 1140 ast::Layer::Value, 1141 )), 1142 FindReferencesSearchScope::CurrentModule => { 1143 let source_information = self.compiler.get_source(&source_module.name)?; 1144 let source_module = self.compiler.get_module_interface(&source_module.name)?; 1145 Some(find_module_references_in_module( 1146 module, 1147 name, 1148 source_module, 1149 source_information, 1150 ast::Layer::Value, 1151 )) 1152 } 1153 }, 1154 Some(Referenced::ModuleType { module, name, .. }) => match search_scope { 1155 FindReferencesSearchScope::AllModules => Some(find_module_references( 1156 module, 1157 name, 1158 self.compiler.project_compiler.get_importable_modules(), 1159 &self.compiler.sources, 1160 ast::Layer::Type, 1161 )), 1162 FindReferencesSearchScope::CurrentModule => { 1163 let source_information = self.compiler.get_source(&source_module.name)?; 1164 let source_module = self.compiler.get_module_interface(&source_module.name)?; 1165 Some(find_module_references_in_module( 1166 module, 1167 name, 1168 source_module, 1169 source_information, 1170 ast::Layer::Type, 1171 )) 1172 } 1173 }, 1174 Some(Referenced::Label { 1175 type_module, 1176 type_name, 1177 label, 1178 .. 1179 }) => match search_scope { 1180 FindReferencesSearchScope::AllModules => Some(find_label_references( 1181 type_module, 1182 type_name, 1183 label, 1184 self.compiler.project_compiler.get_importable_modules(), 1185 &self.compiler.sources, 1186 )), 1187 FindReferencesSearchScope::CurrentModule => { 1188 let source_information = self.compiler.get_source(&source_module.name)?; 1189 let source_module = self.compiler.get_module_interface(&source_module.name)?; 1190 Some(find_label_references_in_module( 1191 type_module, 1192 type_name, 1193 label, 1194 source_module, 1195 source_information, 1196 )) 1197 } 1198 }, 1199 _ => None, 1200 } 1201 } 1202 1203 pub fn find_references( 1204 &mut self, 1205 params: lsp::ReferenceParams, 1206 ) -> Response<Option<Vec<lsp::Location>>> { 1207 self.respond(|this| { 1208 let position = &params.text_document_position_params; 1209 Ok(this.find_references_in_scope(position, FindReferencesSearchScope::AllModules)) 1210 }) 1211 } 1212 1213 pub fn document_highlight( 1214 &mut self, 1215 params: lsp::DocumentHighlightParams, 1216 ) -> Response<Option<Vec<lsp::DocumentHighlight>>> { 1217 self.respond(|this| { 1218 let position = &params.text_document_position_params; 1219 Ok(this 1220 .find_references_in_scope(position, FindReferencesSearchScope::CurrentModule) 1221 .map(|references| { 1222 references 1223 .into_iter() 1224 .map(|loc| lsp::DocumentHighlight { 1225 range: loc.range, 1226 kind: None, 1227 }) 1228 .collect() 1229 })) 1230 }) 1231 } 1232 1233 /// Triggers after the renaming of one or more `.gleam` files, updating any 1234 /// imports to those modules. 1235 pub fn rename_files(&mut self, renames: Vec<(Url, Url)>) -> Response<Option<WorkspaceEdit>> { 1236 self.respond(|this| { 1237 let mut changes = HashMap::new(); 1238 1239 for (old_uri, new_uri) in renames { 1240 let Some(old_name) = this.module_name_for_uri(&old_uri) else { 1241 continue; 1242 }; 1243 let Some(new_name) = this.module_name_for_uri(&new_uri) else { 1244 continue; 1245 }; 1246 rename_module_occurrences( 1247 old_name, 1248 new_name, 1249 this.compiler.project_compiler.get_importable_modules(), 1250 &this.compiler.sources, 1251 &mut changes, 1252 ); 1253 } 1254 1255 Ok(Some(WorkspaceEdit { 1256 changes: Some(changes), 1257 document_changes: None, 1258 change_annotations: None, 1259 })) 1260 }) 1261 } 1262 1263 fn respond<T>(&mut self, handler: impl FnOnce(&mut Self) -> Result<T>) -> Response<T> { 1264 let result = handler(self); 1265 let warnings = self.take_warnings(); 1266 // TODO: test. Ensure hover doesn't report as compiled 1267 let compilation = if self.compiled_since_last_feedback { 1268 let modules = std::mem::take(&mut self.modules_compiled_since_last_feedback); 1269 self.compiled_since_last_feedback = false; 1270 Compilation::Yes(modules) 1271 } else { 1272 Compilation::No 1273 }; 1274 Response { 1275 result, 1276 warnings, 1277 compilation, 1278 } 1279 } 1280 1281 pub fn hover(&mut self, params: lsp::HoverParams) -> Response<Option<Hover>> { 1282 self.respond(|this| { 1283 let params = params.text_document_position_params; 1284 1285 let (lines, found) = match this.node_at_position(&params) { 1286 Some(value) => value, 1287 None => return Ok(None), 1288 }; 1289 1290 let Some(module) = this.module_for_uri(&params.text_document.uri) else { 1291 return Ok(None); 1292 }; 1293 1294 Ok(match found { 1295 Located::Statement(_) => None, // TODO: hover for statement 1296 Located::ModuleFunction(function) => { 1297 Some(hover_for_function_head(function, lines, module)) 1298 } 1299 Located::ModuleConstant(constant) => { 1300 Some(hover_for_module_constant(constant, lines, module)) 1301 } 1302 Located::Constant(constant) => Some(hover_for_constant(constant, lines, module)), 1303 Located::ModuleImport(import) => { 1304 let Some(module) = this.compiler.get_module_interface(&import.module) else { 1305 return Ok(None); 1306 }; 1307 Some(hover_for_module( 1308 module, 1309 import.location, 1310 &lines, 1311 &this.hex_deps, 1312 )) 1313 } 1314 Located::ModuleCustomType(custom_type) => { 1315 Some(hover_for_custom_type(custom_type, lines)) 1316 } 1317 Located::ModuleTypeAlias(_) => None, 1318 Located::VariantConstructorDefinition(constructor) => { 1319 Some(hover_for_constructor(constructor, lines, module)) 1320 } 1321 Located::UnqualifiedImport( 1322 import @ UnqualifiedImport { 1323 name, 1324 module: module_name, 1325 location, 1326 name_position: _, 1327 as_name: _, 1328 }, 1329 ) => this 1330 .compiler 1331 .get_module_interface(module_name.as_str()) 1332 .and_then(|module_interface| { 1333 if import.is_type() { 1334 module_interface.types.get(name).map(|constructor| { 1335 hover_for_annotation( 1336 *location, 1337 constructor.type_.as_ref(), 1338 Some(constructor), 1339 lines, 1340 module, 1341 ) 1342 }) 1343 } else { 1344 module_interface.values.get(name).map(|v| { 1345 let m = if this.hex_deps.contains(&module_interface.package) { 1346 Some(module_interface) 1347 } else { 1348 None 1349 }; 1350 hover_for_imported_value(v, location, lines, m, name, module) 1351 }) 1352 } 1353 }), 1354 Located::Pattern(pattern) => Some(hover_for_pattern(pattern, lines, module)), 1355 Located::PatternSpread { 1356 spread_location, 1357 pattern, 1358 } => { 1359 let range = Some(src_span_to_lsp_range(spread_location, &lines)); 1360 1361 let mut printer = Printer::new(&module.ast.names); 1362 1363 let PatternUnusedArguments { 1364 positional, 1365 labelled, 1366 } = pattern.unused_arguments().unwrap_or_default(); 1367 1368 let positional = positional 1369 .iter() 1370 .map(|type_| format!("- `{}`", printer.print_type(type_))) 1371 .join("\n"); 1372 let labelled = labelled 1373 .iter() 1374 .map(|(label, type_)| { 1375 format!("- `{}: {}`", label, printer.print_type(type_)) 1376 }) 1377 .join("\n"); 1378 1379 let content = match (positional.is_empty(), labelled.is_empty()) { 1380 (true, false) => format!("Unused labelled fields:\n{labelled}"), 1381 (false, true) => format!("Unused positional fields:\n{positional}"), 1382 (_, _) => format!( 1383 "Unused positional fields: 1384{positional} 1385 1386Unused labelled fields: 1387{labelled}" 1388 ), 1389 }; 1390 1391 Some(Hover { 1392 contents: Contents::MarkupContent(MarkupContent { 1393 value: content, 1394 kind: lsp_types::MarkupKind::Markdown, 1395 }), 1396 range, 1397 }) 1398 } 1399 Located::StringPrefixPatternVariable { location, .. } => Some( 1400 hover_for_string_prefix_pattern_variable(location, &lines, module), 1401 ), 1402 Located::Expression { 1403 expression, 1404 position, 1405 } => Some(hover_for_expression( 1406 expression, 1407 position, 1408 lines, 1409 module, 1410 &this.hex_deps, 1411 )), 1412 Located::Arg(arg) => Some(hover_for_function_argument(arg, lines, module)), 1413 Located::FunctionBody(_) => None, 1414 Located::Annotation { ast, type_ } => { 1415 let type_constructor = type_constructor_from_modules( 1416 this.compiler.project_compiler.get_importable_modules(), 1417 type_.clone(), 1418 ); 1419 Some(hover_for_annotation( 1420 ast.location(), 1421 &type_, 1422 type_constructor, 1423 lines, 1424 module, 1425 )) 1426 } 1427 Located::Label { 1428 location, 1429 field_type, 1430 } 1431 | Located::RecordLabelDefinition { 1432 location, 1433 field_type, 1434 .. 1435 } 1436 | Located::RecordLabelUsage { 1437 location, 1438 field_type, 1439 .. 1440 } => Some(hover_for_label(location, field_type, None, lines, module)), 1441 Located::RecordAccessLabel { 1442 location, 1443 field_type, 1444 documentation, 1445 .. 1446 } => Some(hover_for_label( 1447 location, 1448 field_type, 1449 documentation.as_ref(), 1450 lines, 1451 module, 1452 )), 1453 Located::ModuleName { 1454 location, 1455 module_name, 1456 .. 1457 } => { 1458 let Some(module) = this.compiler.get_module_interface(&module_name) else { 1459 return Ok(None); 1460 }; 1461 Some(hover_for_module(module, location, &lines, &this.hex_deps)) 1462 } 1463 1464 Located::ClauseGuard(guard) => Some(hover_for_clause_guard(guard, lines, module)), 1465 1466 Located::TypeVariable { .. } => None, 1467 }) 1468 }) 1469 } 1470 1471 pub(crate) fn signature_help( 1472 &mut self, 1473 params: lsp_types::SignatureHelpParams, 1474 ) -> Response<Option<SignatureHelp>> { 1475 self.respond(|this| { 1476 let Some(module) = 1477 this.module_for_uri(&params.text_document_position_params.text_document.uri) 1478 else { 1479 return Ok(None); 1480 }; 1481 1482 match this.node_at_position(&params.text_document_position_params) { 1483 Some((_lines, Located::Expression { expression, .. })) => { 1484 Ok(signature_help::for_expression(expression, module)) 1485 } 1486 Some((_lines, _located)) => Ok(None), 1487 None => Ok(None), 1488 } 1489 }) 1490 } 1491 1492 fn module_node_at_position( 1493 &self, 1494 params: &lsp::TextDocumentPositionParams, 1495 module: &'a Module, 1496 ) -> Option<(LineNumbers, Located<'a>)> { 1497 let line_numbers = LineNumbers::new(&module.code); 1498 let byte_index = line_numbers.byte_index(params.position); 1499 let node = module.find_node(byte_index); 1500 let node = node?; 1501 Some((line_numbers, node)) 1502 } 1503 1504 fn node_at_position( 1505 &self, 1506 params: &lsp::TextDocumentPositionParams, 1507 ) -> Option<(LineNumbers, Located<'_>)> { 1508 let module = self.module_for_uri(&params.text_document.uri)?; 1509 self.module_node_at_position(params, module) 1510 } 1511 1512 fn module_name_for_uri(&self, uri: &Url) -> Option<EcoString> { 1513 // The to_file_path method is available on these platforms 1514 #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] 1515 let path = uri.to_file_path().expect("URL file"); 1516 1517 #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))] 1518 let path: Utf8PathBuf = uri.path().into(); 1519 1520 let components = path 1521 .strip_prefix(self.paths.root()) 1522 .ok()? 1523 .components() 1524 .skip(1) 1525 .map(|c| c.as_os_str().to_string_lossy()); 1526 let name = Itertools::intersperse(components, "/".into()) 1527 .collect::<String>() 1528 .strip_suffix(".gleam")? 1529 .into(); 1530 Some(name) 1531 } 1532 1533 fn module_for_uri(&self, uri: &Url) -> Option<&Module> { 1534 let module_name = self.module_name_for_uri(uri)?; 1535 self.compiler.modules.get(&module_name) 1536 } 1537 1538 #[cfg(test)] 1539 pub fn path_for_module_name(&self, module_name: &str) -> Utf8PathBuf { 1540 let src_directory = self.paths.src_directory(); 1541 src_directory.join(module_name).with_extension("gleam") 1542 } 1543} 1544 1545fn import_folding_spans( 1546 imports: &[ast::Import<EcoString>], 1547 code: &str, 1548 line_numbers: &LineNumbers, 1549) -> Vec<SrcSpan> { 1550 let mut spans = vec![]; 1551 let mut imports = imports.iter(); 1552 1553 let Some(first_import) = imports.next() else { 1554 return spans; 1555 }; 1556 1557 let mut previous_line = src_span_to_lsp_range( 1558 SrcSpan::new(first_import.location.start, first_import.location.start), 1559 line_numbers, 1560 ) 1561 .start 1562 .line; 1563 let mut current_start = first_import.location.start; 1564 let mut current_end = first_import.location.end; 1565 let mut current_len = 1; 1566 1567 for import in imports { 1568 let next_line = src_span_to_lsp_range( 1569 SrcSpan::new(import.location.start, import.location.start), 1570 line_numbers, 1571 ) 1572 .start 1573 .line; 1574 let separated_by_blank_line = next_line > previous_line + 1; 1575 let between = &code[current_end as usize..import.location.start as usize]; 1576 let has_non_whitespace_between = between.chars().any(|char| !char.is_whitespace()); 1577 1578 if separated_by_blank_line || has_non_whitespace_between { 1579 if current_len > 1 { 1580 spans.push(SrcSpan::new(current_start, current_end)); 1581 } 1582 current_start = import.location.start; 1583 current_end = import.location.end; 1584 current_len = 1; 1585 } else { 1586 current_end = import.location.end; 1587 current_len += 1; 1588 } 1589 1590 previous_line = next_line; 1591 } 1592 1593 if current_len > 1 { 1594 spans.push(SrcSpan::new(current_start, current_end)); 1595 } 1596 1597 spans 1598} 1599 1600fn comment_folding_spans( 1601 comments: &[SrcSpan], 1602 code: &str, 1603 line_numbers: &LineNumbers, 1604 comment_prefix: &str, 1605) -> Vec<SrcSpan> { 1606 let mut spans = vec![]; 1607 let comments = comments.iter(); 1608 1609 let mut comments = comments.map(|span| { 1610 let line = src_span_to_lsp_range(*span, line_numbers).start.line; 1611 let start = line_numbers 1612 .line_starts 1613 .get(line as usize) 1614 .copied() 1615 .unwrap_or_default(); 1616 let end = line_numbers 1617 .line_starts 1618 .get(line as usize + 1) 1619 .copied() 1620 .map(|next_line_start| next_line_start.saturating_sub(1)) 1621 .unwrap_or(line_numbers.length); 1622 1623 // Find prefix (`//`, `///` or `////`), so we use start of the prefix 1624 // as start of comment. This allows us to not fold comments before and 1625 // after some code, like here: 1626 // ```gleam 1627 // // wibble 1628 // // wobble 1629 // wibble // wubble 1630 // ``` 1631 let between = &code[start as usize..end as usize]; 1632 let start = start + between.find(comment_prefix).unwrap_or_default() as u32; 1633 (start, line, end) 1634 }); 1635 1636 let Some((start, line, end)) = comments.next() else { 1637 return spans; 1638 }; 1639 1640 let mut current_start = start; 1641 let mut current_line = line; 1642 let mut current_end = end; 1643 let mut current_len = 1; 1644 1645 for (start, line, end) in comments { 1646 let separated_by_blank_line = line > current_line + 1; 1647 let between = &code[current_end as usize..start as usize]; 1648 let has_non_comments_between_between = between.chars().any(|char| !char.is_whitespace()); 1649 1650 if separated_by_blank_line || has_non_comments_between_between { 1651 if current_len > 1 { 1652 spans.push(SrcSpan::new(current_start, current_end)); 1653 } 1654 current_start = start; 1655 current_end = end; 1656 current_len = 1; 1657 } else { 1658 current_end = end; 1659 current_len += 1; 1660 } 1661 current_line = line; 1662 } 1663 1664 if current_len > 1 { 1665 spans.push(SrcSpan::new(current_start, current_end)); 1666 } 1667 1668 spans 1669} 1670 1671fn folding_range_for_span( 1672 span: SrcSpan, 1673 line_numbers: &LineNumbers, 1674 kind: Option<FoldingRangeKind>, 1675) -> Option<FoldingRange> { 1676 let range = src_span_to_lsp_range(span, line_numbers); 1677 1678 if range.start.line >= range.end.line { 1679 return None; 1680 } 1681 1682 Some(FoldingRange { 1683 start_line: range.start.line, 1684 start_character: None, 1685 end_line: range.end.line, 1686 end_character: None, 1687 kind, 1688 collapsed_text: None, 1689 }) 1690} 1691 1692fn custom_type_symbol( 1693 type_: &CustomType<Arc<Type>>, 1694 line_numbers: &LineNumbers, 1695 module: &Module, 1696) -> DocumentSymbol { 1697 let constructors = type_ 1698 .constructors 1699 .iter() 1700 .map(|constructor| { 1701 let mut arguments = vec![]; 1702 1703 // List named arguments as field symbols. 1704 for argument in &constructor.arguments { 1705 let Some((label_location, label)) = &argument.label else { 1706 continue; 1707 }; 1708 1709 let full_arg_span = match argument.doc { 1710 Some((doc_position, _)) => { 1711 SrcSpan::new(get_doc_marker_position(doc_position), argument.location.end) 1712 } 1713 None => argument.location, 1714 }; 1715 1716 // The 'deprecated' field is deprecated, but we have to specify it anyway 1717 // to be able to construct the 'DocumentSymbol' type, so 1718 // we suppress the warning. We specify 'None' as specifying 'Some' 1719 // is what is actually deprecated. 1720 #[allow(deprecated)] 1721 arguments.push(DocumentSymbol { 1722 name: label.to_string(), 1723 detail: Some( 1724 Printer::new(&module.ast.names) 1725 .print_type(&argument.type_) 1726 .to_string(), 1727 ), 1728 kind: SymbolKind::Field, 1729 tags: None, 1730 deprecated: None, 1731 range: src_span_to_lsp_range(full_arg_span, line_numbers), 1732 selection_range: src_span_to_lsp_range(*label_location, line_numbers), 1733 children: None, 1734 }); 1735 } 1736 1737 // Start from the documentation if available, otherwise from the constructor's name, 1738 // all the way to the end of its arguments. 1739 let full_constructor_span = SrcSpan { 1740 start: constructor 1741 .documentation 1742 .as_ref() 1743 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 1744 .unwrap_or(constructor.location.start), 1745 1746 end: constructor.location.end, 1747 }; 1748 1749 // The 'deprecated' field is deprecated, but we have to specify it anyway 1750 // to be able to construct the 'DocumentSymbol' type, so 1751 // we suppress the warning. We specify 'None' as specifying 'Some' 1752 // is what is actually deprecated. 1753 #[allow(deprecated)] 1754 DocumentSymbol { 1755 name: constructor.name.to_string(), 1756 detail: None, 1757 kind: if constructor.arguments.is_empty() { 1758 SymbolKind::EnumMember 1759 } else { 1760 SymbolKind::Constructor 1761 }, 1762 tags: make_deprecated_symbol_tag(&constructor.deprecation), 1763 deprecated: None, 1764 range: src_span_to_lsp_range(full_constructor_span, line_numbers), 1765 selection_range: src_span_to_lsp_range(constructor.name_location, line_numbers), 1766 children: if arguments.is_empty() { 1767 None 1768 } else { 1769 Some(arguments) 1770 }, 1771 } 1772 }) 1773 .collect_vec(); 1774 1775 // The type's location, by default, ranges from "(pub) type" to the end of its name. 1776 // We need it to range to the end of its constructors instead for the full symbol range. 1777 // We also include documentation, if available, by LSP convention. 1778 let full_type_span = SrcSpan { 1779 start: type_ 1780 .documentation 1781 .as_ref() 1782 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 1783 .unwrap_or(type_.location.start), 1784 1785 end: type_.end_position, 1786 }; 1787 1788 // The 'deprecated' field is deprecated, but we have to specify it anyway 1789 // to be able to construct the 'DocumentSymbol' type, so 1790 // we suppress the warning. We specify 'None' as specifying 'Some' 1791 // is what is actually deprecated. 1792 #[allow(deprecated)] 1793 DocumentSymbol { 1794 name: type_.name.to_string(), 1795 detail: None, 1796 kind: SymbolKind::Class, 1797 tags: make_deprecated_symbol_tag(&type_.deprecation), 1798 deprecated: None, 1799 range: src_span_to_lsp_range(full_type_span, line_numbers), 1800 selection_range: src_span_to_lsp_range(type_.name_location, line_numbers), 1801 children: if constructors.is_empty() { 1802 None 1803 } else { 1804 Some(constructors) 1805 }, 1806 } 1807} 1808 1809fn hover_for_pattern(pattern: &TypedPattern, line_numbers: LineNumbers, module: &Module) -> Hover { 1810 let documentation = pattern.get_documentation().unwrap_or_default(); 1811 1812 // Show the type of the hovered node to the user 1813 let type_ = Printer::new(&module.ast.names).print_type(pattern.type_().as_ref()); 1814 let contents = format!( 1815 "```gleam 1816{type_} 1817``` 1818{documentation}" 1819 ); 1820 Hover { 1821 contents: Contents::MarkedString(MarkedString::String(contents)), 1822 range: Some(src_span_to_lsp_range(pattern.location(), &line_numbers)), 1823 } 1824} 1825 1826fn get_function_type(fun: &TypedFunction) -> Type { 1827 Type::Fn { 1828 arguments: fun 1829 .arguments 1830 .iter() 1831 .map(|argument| argument.type_.clone()) 1832 .collect(), 1833 return_: fun.return_type.clone(), 1834 } 1835} 1836 1837fn hover_for_function_head( 1838 fun: &TypedFunction, 1839 line_numbers: LineNumbers, 1840 module: &Module, 1841) -> Hover { 1842 let empty_str = EcoString::from(""); 1843 let documentation = fun 1844 .documentation 1845 .as_ref() 1846 .map(|(_, doc)| doc) 1847 .unwrap_or(&empty_str); 1848 let function_type = get_function_type(fun); 1849 let formatted_type = Printer::new(&module.ast.names).print_type(&function_type); 1850 let contents = format!( 1851 "```gleam 1852{formatted_type} 1853``` 1854{documentation}" 1855 ); 1856 Hover { 1857 contents: Contents::MarkedString(MarkedString::String(contents)), 1858 range: Some(src_span_to_lsp_range(fun.location, &line_numbers)), 1859 } 1860} 1861 1862fn hover_for_function_argument( 1863 argument: &TypedArg, 1864 line_numbers: LineNumbers, 1865 module: &Module, 1866) -> Hover { 1867 let type_ = Printer::new(&module.ast.names).print_type(&argument.type_); 1868 let contents = format!("```gleam\n{type_}\n```"); 1869 Hover { 1870 contents: Contents::MarkedString(MarkedString::String(contents)), 1871 range: Some(src_span_to_lsp_range(argument.location, &line_numbers)), 1872 } 1873} 1874 1875fn hover_for_annotation( 1876 location: SrcSpan, 1877 annotation_type: &Type, 1878 type_constructor: Option<&TypeConstructor>, 1879 line_numbers: LineNumbers, 1880 module: &Module, 1881) -> Hover { 1882 let empty_str = EcoString::from(""); 1883 let documentation = type_constructor 1884 .and_then(|constructor| constructor.documentation.as_ref()) 1885 .unwrap_or(&empty_str); 1886 // If a user is hovering an annotation, it's not very useful to show the 1887 // local representation of that type, since that's probably what they see 1888 // in the source code anyway. So here, we print the raw type, 1889 // which is probably more helpful. 1890 let type_ = Printer::new(&module.ast.names).print_type_without_aliases(annotation_type); 1891 let contents = format!( 1892 "```gleam 1893{type_} 1894``` 1895{documentation}" 1896 ); 1897 Hover { 1898 contents: Contents::MarkedString(MarkedString::String(contents)), 1899 range: Some(src_span_to_lsp_range(location, &line_numbers)), 1900 } 1901} 1902 1903fn hover_for_label( 1904 location: SrcSpan, 1905 type_: Arc<Type>, 1906 documentation: Option<&EcoString>, 1907 line_numbers: LineNumbers, 1908 module: &Module, 1909) -> Hover { 1910 let type_ = Printer::new(&module.ast.names).print_type(&type_); 1911 let contents = match documentation { 1912 Some(documentation) => format!("```gleam\n{type_}\n```\n{documentation}"), 1913 None => format!("```gleam\n{type_}\n```"), 1914 }; 1915 Hover { 1916 contents: Contents::MarkedString(MarkedString::String(contents)), 1917 range: Some(src_span_to_lsp_range(location, &line_numbers)), 1918 } 1919} 1920 1921fn hover_for_module_constant( 1922 constant: &ModuleConstant<Arc<Type>>, 1923 line_numbers: LineNumbers, 1924 module: &Module, 1925) -> Hover { 1926 let empty_str = EcoString::from(""); 1927 let type_ = Printer::new(&module.ast.names).print_type(&constant.type_); 1928 let documentation = constant 1929 .documentation 1930 .as_ref() 1931 .map(|(_, doc)| doc) 1932 .unwrap_or(&empty_str); 1933 let contents = format!("```gleam\n{type_}\n```\n{documentation}"); 1934 Hover { 1935 contents: Contents::MarkedString(MarkedString::String(contents)), 1936 range: Some(src_span_to_lsp_range(constant.location, &line_numbers)), 1937 } 1938} 1939 1940fn hover_for_constant( 1941 constant: &TypedConstant, 1942 line_numbers: LineNumbers, 1943 module: &Module, 1944) -> Hover { 1945 let type_ = Printer::new(&module.ast.names).print_type(&constant.type_()); 1946 let contents = format!("```gleam\n{type_}\n```"); 1947 Hover { 1948 contents: Contents::MarkedString(MarkedString::String(contents)), 1949 range: Some(src_span_to_lsp_range(constant.location(), &line_numbers)), 1950 } 1951} 1952 1953fn hover_for_clause_guard( 1954 guard: &TypedClauseGuard, 1955 line_numbers: LineNumbers, 1956 module: &Module, 1957) -> Hover { 1958 let type_ = Printer::new(&module.ast.names).print_type(&guard.type_()); 1959 let contents = format!("```gleam\n{type_}\n```"); 1960 Hover { 1961 contents: Contents::MarkedString(MarkedString::String(contents)), 1962 range: Some(src_span_to_lsp_range(guard.location(), &line_numbers)), 1963 } 1964} 1965 1966fn hover_for_string_prefix_pattern_variable( 1967 location: SrcSpan, 1968 lines: &LineNumbers, 1969 module: &Module, 1970) -> Hover { 1971 let type_ = Printer::new(&module.ast.names).print_type(&type_::string()); 1972 let contents = format!("```gleam\n{type_}\n```"); 1973 Hover { 1974 contents: Contents::MarkedString(MarkedString::String(contents)), 1975 range: Some(src_span_to_lsp_range(location, lines)), 1976 } 1977} 1978 1979fn hover_for_expression<'a>( 1980 expression: &'a TypedExpr, 1981 position: ExpressionPosition<'a>, 1982 line_numbers: LineNumbers, 1983 module: &Module, 1984 hex_deps: &HashSet<EcoString>, 1985) -> Hover { 1986 let documentation = expression.get_documentation().unwrap_or_default(); 1987 1988 let link_section = get_expr_qualified_name(expression) 1989 .and_then(|(module_name, name)| { 1990 get_hexdocs_link_section(module_name, name, &module.ast, hex_deps) 1991 }) 1992 .unwrap_or("".to_string()); 1993 1994 // Show the type of the hovered node to the user 1995 let mut printer = Printer::new(&module.ast.names); 1996 let type_ = printer.print_type(expression.type_().as_ref()); 1997 1998 // If the expression is a record update and there's record fields that are 1999 // begin implicitly updated, then we want to list them in the hover. 2000 let unchanged_record_update_fields = match position { 2001 ExpressionPosition::Expression | ExpressionPosition::ArgumentOrLabel { .. } => "", 2002 2003 ExpressionPosition::UpdatedRecord { 2004 unchanged_record_fields, 2005 } if !unchanged_record_fields.is_empty() => &format!( 2006 "Unchanged record fields:\n{}", 2007 unchanged_record_fields 2008 .iter() 2009 .map(|(label, type_)| format!("- `{}: {}`", label, printer.print_type(type_))) 2010 .join("\n") 2011 ), 2012 ExpressionPosition::UpdatedRecord { .. } => "", 2013 }; 2014 2015 let description = [documentation, unchanged_record_update_fields, &link_section] 2016 .iter() 2017 .filter(|string| !string.is_empty()) 2018 .join("\n\n"); 2019 2020 let contents = format!( 2021 "```gleam 2022{type_} 2023``` 2024{description}" 2025 ); 2026 2027 Hover { 2028 contents: Contents::MarkedString(MarkedString::String(contents)), 2029 range: Some(src_span_to_lsp_range(expression.location(), &line_numbers)), 2030 } 2031} 2032 2033fn hover_for_imported_value( 2034 value: &ValueConstructor, 2035 location: &SrcSpan, 2036 line_numbers: LineNumbers, 2037 hex_module_imported_from: Option<&ModuleInterface>, 2038 name: &EcoString, 2039 module: &Module, 2040) -> Hover { 2041 let documentation = value.get_documentation().unwrap_or_default(); 2042 2043 let link_section = hex_module_imported_from.map_or("".to_string(), |m| { 2044 format_hexdocs_link_section(m.package.as_str(), m.name.as_str(), Some(name)) 2045 }); 2046 2047 // Show the type of the hovered node to the user 2048 let type_ = Printer::new(&module.ast.names).print_type(value.type_.as_ref()); 2049 let contents = format!( 2050 "```gleam 2051{type_} 2052``` 2053{documentation}{link_section}" 2054 ); 2055 Hover { 2056 contents: Contents::MarkedString(MarkedString::String(contents)), 2057 range: Some(src_span_to_lsp_range(*location, &line_numbers)), 2058 } 2059} 2060 2061fn hover_for_module( 2062 module: &ModuleInterface, 2063 location: SrcSpan, 2064 line_numbers: &LineNumbers, 2065 hex_deps: &HashSet<EcoString>, 2066) -> Hover { 2067 let documentation = module.documentation.join("\n"); 2068 let name = &module.name; 2069 2070 let link_section = if hex_deps.contains(&module.package) { 2071 format_hexdocs_link_section(&module.package, name, None) 2072 } else { 2073 String::new() 2074 }; 2075 2076 let contents = format!( 2077 "```gleam 2078{name} 2079``` 2080{documentation} 2081{link_section}", 2082 ); 2083 Hover { 2084 contents: Contents::MarkedString(MarkedString::String(contents)), 2085 range: Some(src_span_to_lsp_range(location, line_numbers)), 2086 } 2087} 2088 2089fn hover_for_custom_type(type_: &CustomType<Arc<Type>>, line_numbers: LineNumbers) -> Hover { 2090 let name = &type_.name; 2091 let documentation = type_ 2092 .documentation 2093 .as_ref() 2094 .map(|(_, documentation)| documentation.clone()) 2095 .unwrap_or_default(); 2096 2097 let contents = format!("```gleam\n{name}\n```\n{documentation}"); 2098 Hover { 2099 contents: Contents::MarkedString(MarkedString::String(contents)), 2100 range: Some(src_span_to_lsp_range(type_.full_location(), &line_numbers)), 2101 } 2102} 2103 2104fn hover_for_constructor( 2105 constructor: &TypedRecordConstructor, 2106 line_numbers: LineNumbers, 2107 module: &Module, 2108) -> Hover { 2109 let mut printer = Printer::new(&module.ast.names); 2110 2111 let arguments = constructor 2112 .arguments 2113 .iter() 2114 .map(|argument| match &argument.label { 2115 Some((_, label)) => eco_format!("{label}: {}", printer.print_type(&argument.type_)), 2116 None => printer.print_type(&argument.type_), 2117 }) 2118 .join(", "); 2119 2120 let documentation = constructor 2121 .documentation 2122 .as_ref() 2123 .map(|(_, documentation)| documentation.clone()) 2124 .unwrap_or_default(); 2125 2126 let constructor_doc = if arguments.is_empty() { 2127 constructor.name.clone() 2128 } else { 2129 eco_format!("{}({arguments})", constructor.name) 2130 }; 2131 2132 let contents = format!("```gleam\n{constructor_doc}\n```\n{documentation}"); 2133 Hover { 2134 contents: Contents::MarkedString(MarkedString::String(contents)), 2135 range: Some(src_span_to_lsp_range(constructor.location, &line_numbers)), 2136 } 2137} 2138 2139/// Returns true if any part of either range overlaps with the other. 2140pub fn overlaps(a: Range, b: Range) -> bool { 2141 position_within(a.start, b) 2142 || position_within(a.end, b) 2143 || position_within(b.start, a) 2144 || position_within(b.end, a) 2145} 2146 2147/// Returns true if the first range is within the second range. 2148/// The ranges might touch on their extremes and still be considered one within 2149/// the other! 2150/// 2151/// `within(a, b)` is true in all of these cases: 2152/// 2153/// - ```txt 2154/// |------| b 2155/// |---| a 2156/// ``` 2157/// - ```txt 2158/// |------| b 2159/// |---| a 2160/// ``` 2161/// - ```txt 2162/// |------| b 2163/// |---| a 2164/// ``` 2165/// - ```txt 2166/// |------| b 2167/// |------| a 2168/// ``` 2169/// 2170pub fn within(a: Range, b: Range) -> bool { 2171 position_within(a.start, b) && position_within(a.end, b) 2172} 2173 2174/// Returns true if the first range is completely within the second range. 2175/// The range cannot have any extreme in common to be considered completely 2176/// within another. 2177/// 2178/// `completely_within(a, b)` is true this case: 2179/// 2180/// - ```txt 2181/// |------| b 2182/// |---| a 2183/// ``` 2184/// 2185/// And `completely_within(a, b)` is false in all these cases: 2186/// 2187/// - ```txt 2188/// |------| b 2189/// |---| a 2190/// ``` 2191/// - ```txt 2192/// |------| b 2193/// |---| a 2194/// ``` 2195/// - ```txt 2196/// |------| b 2197/// |------| a 2198/// ``` 2199/// 2200pub fn completely_within(a: Range, b: Range) -> bool { 2201 a.start > b.start && a.start < b.end && a.end > b.start && a.end < b.end 2202} 2203 2204// Returns true if a position is within a range. 2205pub fn position_within(position: Position, range: Range) -> bool { 2206 position >= range.start && position <= range.end 2207} 2208 2209/// Builds the code action to assign an unused value to `_`. 2210/// 2211fn code_action_unused_values( 2212 module: &Module, 2213 line_numbers: &LineNumbers, 2214 params: &lsp::CodeActionParams, 2215 actions: &mut Vec<CodeAction>, 2216) { 2217 let uri = &params.text_document.uri; 2218 let mut unused_values: Vec<&SrcSpan> = module 2219 .ast 2220 .type_info 2221 .warnings 2222 .iter() 2223 .filter_map(|warning| { 2224 if let type_::Warning::ImplicitlyDiscardedResult { location } = warning { 2225 Some(location) 2226 } else { 2227 None 2228 } 2229 }) 2230 .collect(); 2231 2232 if unused_values.is_empty() { 2233 return; 2234 } 2235 2236 // Sort spans by start position, with longer spans coming first 2237 unused_values.sort_by_key(|span| (span.start, -(span.len() as i64))); 2238 2239 let mut processed_lsp_range = Vec::new(); 2240 2241 for unused in unused_values { 2242 let SrcSpan { start, end } = *unused; 2243 let hover_range = src_span_to_lsp_range(SrcSpan::new(start, end), line_numbers); 2244 2245 // Check if this span is contained within any previously processed span 2246 if processed_lsp_range 2247 .iter() 2248 .any(|&prev_lsp_range| within(hover_range, prev_lsp_range)) 2249 { 2250 continue; 2251 } 2252 2253 // Check if the cursor is within this span 2254 if !within(params.range, hover_range) { 2255 continue; 2256 } 2257 2258 let edit = TextEdit { 2259 range: src_span_to_lsp_range(SrcSpan::new(start, start), line_numbers), 2260 new_text: "let _ = ".into(), 2261 }; 2262 2263 CodeActionBuilder::new("Assign unused Result value to `_`") 2264 .kind(lsp_types::CodeActionKind::QuickFix) 2265 .changes(uri.clone(), vec![edit]) 2266 .preferred(true) 2267 .push_to(actions); 2268 2269 processed_lsp_range.push(hover_range); 2270 } 2271} 2272 2273struct NameCorrection { 2274 pub location: SrcSpan, 2275 pub correction: EcoString, 2276} 2277 2278fn code_action_fix_names( 2279 module: &Module, 2280 line_numbers: &LineNumbers, 2281 params: &lsp::CodeActionParams, 2282 error: &Option<Error>, 2283 actions: &mut Vec<CodeAction>, 2284) { 2285 let uri = &params.text_document.uri; 2286 let Some(errors) = type_errors_for_module(error, module) else { 2287 return; 2288 }; 2289 let name_corrections = errors 2290 .iter() 2291 .filter_map(|error| { 2292 if let type_::Error::BadName { 2293 location, 2294 name, 2295 kind, 2296 } = error 2297 { 2298 Some(NameCorrection { 2299 correction: correct_name_case(name, *kind), 2300 location: *location, 2301 }) 2302 } else { 2303 None 2304 } 2305 }) 2306 .collect_vec(); 2307 2308 if name_corrections.is_empty() { 2309 return; 2310 } 2311 2312 for name_correction in name_corrections { 2313 let NameCorrection { 2314 location, 2315 correction, 2316 } = name_correction; 2317 2318 let range = src_span_to_lsp_range(location, line_numbers); 2319 // Check if the user's cursor is on the invalid name 2320 if overlaps(params.range, range) { 2321 let edit = TextEdit { 2322 range, 2323 new_text: correction.to_string(), 2324 }; 2325 2326 CodeActionBuilder::new(&format!("Rename to {correction}")) 2327 .kind(lsp_types::CodeActionKind::QuickFix) 2328 .changes(uri.clone(), vec![edit]) 2329 .preferred(true) 2330 .push_to(actions); 2331 } 2332 } 2333} 2334 2335fn get_expr_qualified_name(expression: &TypedExpr) -> Option<(&EcoString, &EcoString)> { 2336 match expression { 2337 TypedExpr::Var { 2338 name, constructor, .. 2339 } if constructor.publicity.is_importable() => match &constructor.variant { 2340 ValueConstructorVariant::ModuleFn { 2341 module: module_name, 2342 .. 2343 } => Some((module_name, name)), 2344 2345 ValueConstructorVariant::ModuleConstant { 2346 module: module_name, 2347 .. 2348 } => Some((module_name, name)), 2349 2350 ValueConstructorVariant::LocalVariable { .. } 2351 | ValueConstructorVariant::Record { .. } => None, 2352 }, 2353 2354 TypedExpr::ModuleSelect { 2355 label, module_name, .. 2356 } => Some((module_name, label)), 2357 2358 TypedExpr::Int { .. } 2359 | TypedExpr::Float { .. } 2360 | TypedExpr::String { .. } 2361 | TypedExpr::Block { .. } 2362 | TypedExpr::Pipeline { .. } 2363 | TypedExpr::Var { .. } 2364 | TypedExpr::Fn { .. } 2365 | TypedExpr::List { .. } 2366 | TypedExpr::Call { .. } 2367 | TypedExpr::BinOp { .. } 2368 | TypedExpr::Case { .. } 2369 | TypedExpr::RecordAccess { .. } 2370 | TypedExpr::PositionalAccess { .. } 2371 | TypedExpr::Tuple { .. } 2372 | TypedExpr::TupleIndex { .. } 2373 | TypedExpr::Todo { .. } 2374 | TypedExpr::Panic { .. } 2375 | TypedExpr::Echo { .. } 2376 | TypedExpr::BitArray { .. } 2377 | TypedExpr::RecordUpdate { .. } 2378 | TypedExpr::NegateBool { .. } 2379 | TypedExpr::NegateInt { .. } 2380 | TypedExpr::Invalid { .. } => None, 2381 } 2382} 2383 2384fn format_hexdocs_link_section( 2385 package_name: &str, 2386 module_name: &str, 2387 name: Option<&str>, 2388) -> String { 2389 let package_name = package_name.replace('_', "-"); 2390 let link = match name { 2391 Some(name) => format!("https://{package_name}.hexdocs.pm/{module_name}.html#{name}"), 2392 None => format!("https://{package_name}.hexdocs.pm/{module_name}.html"), 2393 }; 2394 format!("\nView on [HexDocs]({link})") 2395} 2396 2397fn get_hexdocs_link_section( 2398 module_name: &str, 2399 name: &str, 2400 ast: &TypedModule, 2401 hex_deps: &HashSet<EcoString>, 2402) -> Option<String> { 2403 let package_name = ast.definitions.imports.iter().find_map(|import| { 2404 if import.module == module_name && hex_deps.contains(&import.package) { 2405 Some(&import.package) 2406 } else { 2407 None 2408 } 2409 })?; 2410 2411 Some(format_hexdocs_link_section( 2412 package_name, 2413 module_name, 2414 Some(name), 2415 )) 2416} 2417 2418/// Converts the source start position of a documentation comment's contents into 2419/// the position of the leading slash in its marker ('///'). 2420fn get_doc_marker_position(content_pos: u32) -> u32 { 2421 content_pos.saturating_sub(3) 2422} 2423 2424fn make_deprecated_symbol_tag(deprecation: &Deprecation) -> Option<Vec<SymbolTag>> { 2425 deprecation 2426 .is_deprecated() 2427 .then(|| vec![SymbolTag::Deprecated]) 2428} 2429 2430enum FindReferencesSearchScope { 2431 AllModules, 2432 CurrentModule, 2433}