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