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