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