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