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