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