1use lsp_types::{Hover, HoverParams, Position, Range};
2
3use super::*;
4
5fn hover(tester: TestProject<'_>, position: Position) -> Option<Hover> {
6 tester.at(position, |engine, param, _| {
7 let params = HoverParams {
8 text_document_position_params: param,
9 work_done_progress_params: Default::default(),
10 };
11 let response = engine.hover(params);
12
13 response.result.unwrap()
14 })
15}
16
17pub fn show_hover(code: &str, range: Range, position: Position) -> String {
18 let Range { start, end } = range;
19
20 // When we display the over range the end character is always excluded!
21 let end = Position::new(end.line, end.character);
22
23 let mut buffer: String = "".into();
24 for (line_number, line) in code.lines().enumerate() {
25 let mut underline: String = "".into();
26 let mut underline_empty = true;
27
28 for (column_number, _) in line.chars().enumerate() {
29 let current_position = Position::new(line_number as u32, column_number as u32);
30 if current_position == position {
31 underline_empty = false;
32 underline.push('↑');
33 } else if start.le(¤t_position) && current_position.lt(&end) {
34 underline_empty = false;
35 underline.push('▔');
36 } else {
37 underline.push(' ');
38 }
39 }
40
41 buffer.push_str(line);
42 if !underline_empty {
43 buffer.push('\n');
44 buffer.push_str(&underline);
45 }
46 buffer.push('\n');
47 }
48
49 buffer
50}
51
52#[macro_export]
53macro_rules! assert_hover {
54 ($code:literal, $position:expr $(,)?) => {
55 let project = TestProject::for_source($code);
56 assert_hover!(project, $position);
57 };
58
59 ($project:expr, $position:expr $(,)?) => {
60 let src = $project.src;
61 let position = $position.find_position(src);
62 let result = hover($project, position).expect("no hover produced");
63 let pretty_hover = show_hover(src, result.range.expect("hover with no range"), position);
64 let output = format!(
65 "{}\n\n----- Hover content -----\n{:#?}",
66 pretty_hover, result.contents
67 );
68 insta::assert_snapshot!(insta::internals::AutoName, output, src);
69 };
70}
71
72#[test]
73fn hover_function_definition() {
74 assert_hover!(
75 "
76fn add_2(x) {
77 x + 2
78}
79",
80 find_position_of("add_2")
81 );
82}
83
84#[test]
85fn hover_local_function() {
86 assert_hover!(
87 "
88fn my_fn() {
89 Nil
90}
91
92fn main() {
93 my_fn
94}
95",
96 find_position_of("my_fn").under_char('y').nth_occurrence(2)
97 );
98}
99
100// https://github.com/gleam-lang/gleam/issues/2654
101#[test]
102fn hover_local_function_in_pipe() {
103 assert_hover!(
104 "
105fn add1(num: Int) -> Int {
106 num + 1
107}
108
109pub fn main() {
110 add1(1)
111
112 1
113 |> add1
114 |> add1
115 |> add1
116}
117",
118 find_position_of("add1")
119 .with_char_offset(1)
120 .nth_occurrence(2)
121 );
122}
123
124// https://github.com/gleam-lang/gleam/issues/2654
125#[test]
126fn hover_local_function_in_pipe_1() {
127 assert_hover!(
128 "
129fn add1(num: Int) -> Int {
130 num + 1
131}
132
133pub fn main() {
134 add1(1)
135
136 1
137 |> add1
138 |> add1
139 |> add1
140}
141",
142 find_position_of("add1")
143 .with_char_offset(2)
144 .nth_occurrence(3)
145 );
146}
147
148// https://github.com/gleam-lang/gleam/issues/2654
149#[test]
150fn hover_local_function_in_pipe_2() {
151 assert_hover!(
152 "
153fn add1(num: Int) -> Int {
154 num + 1
155}
156
157pub fn main() {
158 add1(1)
159
160 1
161 |> add1
162 |> add1
163 |> add1
164}
165",
166 find_position_of("add1")
167 .with_char_offset(2)
168 .nth_occurrence(4)
169 );
170}
171
172// https://github.com/gleam-lang/gleam/issues/2654
173#[test]
174fn hover_local_function_in_pipe_3() {
175 assert_hover!(
176 "
177fn add1(num: Int) -> Int {
178 num + 1
179}
180
181pub fn main() {
182 add1(1)
183
184 1
185 |> add1
186 |> add1
187 |> add1
188}
189",
190 find_position_of("add1")
191 .with_char_offset(2)
192 .nth_occurrence(5)
193 );
194}
195
196#[test]
197fn hover_imported_function() {
198 let code = "
199import example_module
200fn main() {
201 example_module.my_fn
202}
203";
204
205 assert_hover!(
206 TestProject::for_source(code).add_module("example_module", "pub fn my_fn() { Nil }"),
207 find_position_of("my_fn").under_char('_'),
208 );
209}
210
211#[test]
212fn hover_external_imported_function() {
213 let code = "
214import example_module
215fn main() {
216 example_module.my_fn
217}
218";
219
220 assert_hover!(
221 TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"),
222 find_position_of("my_fn").under_char('_'),
223 );
224}
225
226#[test]
227fn hover_external_imported_unqualified_function() {
228 let code = "
229import example_module.{my_fn}
230fn main() {
231 my_fn
232}
233";
234
235 assert_hover!(
236 TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"),
237 find_position_of("my_fn").under_char('f').nth_occurrence(2),
238 );
239}
240
241#[test]
242fn hover_external_imported_function_renamed_module() {
243 let code = "
244import example_module as renamed_module
245fn main() {
246 renamed_module.my_fn
247}
248";
249
250 assert_hover!(
251 TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"),
252 find_position_of("my_fn").under_char('f'),
253 );
254}
255
256#[test]
257fn hover_external_unqualified_imported_function_renamed_module() {
258 let code = "
259import example_module.{my_fn} as renamed_module
260fn main() {
261 my_fn
262}
263";
264
265 assert_hover!(
266 TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"),
267 find_position_of("my_fn").under_char('_').nth_occurrence(2),
268 );
269}
270
271#[test]
272fn hover_external_imported_function_nested_module() {
273 // Example of HexDocs link with nested modules: https://hexdocs.pm/lustre/lustre/element/svg.html
274 let code = "
275import my/nested/example_module
276fn main() {
277 example_module.my_fn
278}
279";
280
281 assert_hover!(
282 TestProject::for_source(code)
283 .add_hex_module("my/nested/example_module", "pub fn my_fn() { Nil }"),
284 find_position_of("my_fn").under_char('f'),
285 );
286}
287
288#[test]
289fn hover_external_imported_ffi_renamed_function() {
290 let code = r#"
291import example_module
292fn main() {
293 example_module.my_fn
294}
295"#;
296
297 let hex_module = r#"
298@external(erlang, "my_mod_ffi", "renamed_fn")
299pub fn my_fn() -> Nil
300"#;
301
302 assert_hover!(
303 TestProject::for_source(code).add_hex_module("example_module", hex_module,),
304 find_position_of("my_fn").under_char('f'),
305 );
306}
307
308#[test]
309fn hover_external_imported_constants() {
310 let code = "
311import example_module
312fn main() {
313 example_module.my_const
314}
315";
316
317 assert_hover!(
318 TestProject::for_source(code).add_hex_module("example_module", "pub const my_const = 42"),
319 find_position_of("my_const").under_char('_'),
320 );
321}
322
323#[test]
324fn hover_external_imported_unqualified_constants() {
325 let code = "
326import example_module.{my_const}
327fn main() {
328 my_const
329}
330";
331
332 assert_hover!(
333 TestProject::for_source(code).add_hex_module("example_module", "pub const my_const = 42"),
334 find_position_of("my_const")
335 .under_char('c')
336 .nth_occurrence(2),
337 );
338}
339
340#[test]
341fn hover_external_value_with_two_modules_same_name() {
342 let code = "
343import a/example_module as _
344import b/example_module
345fn main() {
346 example_module.my_const
347}
348";
349
350 assert_hover!(
351 TestProject::for_source(code)
352 .add_hex_module("a/example_module", "pub const my_const = 42")
353 .add_hex_module("b/example_module", "pub const my_const = 42"),
354 find_position_of("my_const").under_char('c'),
355 );
356}
357
358#[test]
359fn hover_external_function_with_another_value_same_name() {
360 let code = "
361import a/example_module.{my_const as discarded}
362import b/example_module.{my_const} as _
363fn main() {
364 my_const
365}
366";
367
368 assert_hover!(
369 TestProject::for_source(code)
370 .add_hex_module("a/example_module", "pub const my_const = 42")
371 .add_hex_module("b/example_module", "pub const my_const = 42"),
372 find_position_of("my_const")
373 .under_char('o')
374 .nth_occurrence(3),
375 );
376}
377
378#[test]
379fn hover_function_definition_with_docs() {
380 assert_hover!(
381 "
382/// Exciting documentation
383/// Maybe even multiple lines
384fn append(x, y) {
385 x <> y
386}
387",
388 find_position_of("append")
389 );
390}
391
392#[test]
393fn hover_function_argument() {
394 assert_hover!(
395 "
396/// Exciting documentation
397/// Maybe even multiple lines
398fn append(x, y) {
399 x <> y
400}
401",
402 find_position_of("append(x, y)").under_char('x')
403 );
404}
405
406#[test]
407fn hover_function_body() {
408 let code = "
409/// Exciting documentation
410/// Maybe even multiple lines
411fn append(x, y) {
412 x <> y
413}
414";
415
416 assert_eq!(
417 hover(TestProject::for_source(code), Position::new(4, 1)),
418 None
419 );
420}
421
422#[test]
423fn hover_expressions_in_function_body() {
424 assert_hover!(
425 "
426fn append(x, y) {
427 x <> y
428}
429",
430 find_position_of("x").nth_occurrence(2)
431 );
432}
433
434#[test]
435fn hover_module_constant() {
436 assert_hover!(
437 "
438/// Exciting documentation
439/// Maybe even multiple lines
440const one = 1
441",
442 find_position_of("one")
443 );
444}
445
446#[test]
447fn hover_variable_in_use_expression() {
448 assert_hover!(
449 "
450fn b(fun: fn(Int) -> String) {
451 fun(42)
452}
453
454fn do_stuff() {
455 let c = \"done\"
456
457 use a <- b
458 c
459}
460",
461 find_position_of("use a").under_last_char()
462 );
463}
464
465#[test]
466fn hover_variable_in_use_expression_1() {
467 assert_hover!(
468 "
469fn b(fun: fn(Int) -> String) {
470 fun(42)
471}
472
473fn do_stuff() {
474 let c = \"done\"
475
476 use a <- b
477 c
478}
479",
480 find_position_of("b").nth_occurrence(2)
481 );
482}
483
484#[test]
485fn hover_variable_in_use_expression_2() {
486 assert_hover!(
487 "
488fn b(fun: fn(Int) -> String) {
489 fun(42)
490}
491
492fn do_stuff() {
493 let c = \"done\"
494
495 use a <- b
496 c
497}
498",
499 find_position_of("c").nth_occurrence(2)
500 );
501}
502
503#[test]
504fn hover_function_arg_annotation_2() {
505 assert_hover!(
506 "
507/// Exciting documentation
508/// Maybe even multiple lines
509fn append(x: String, y: String) -> String {
510 x <> y
511}
512",
513 find_position_of("String").under_char('n')
514 );
515}
516
517#[test]
518fn hover_function_return_annotation() {
519 assert_hover!(
520 "
521/// Exciting documentation
522/// Maybe even multiple lines
523fn append(x: String, y: String) -> String {
524 x <> y
525}
526",
527 find_position_of("String").under_char('n').nth_occurrence(3)
528 );
529}
530
531#[test]
532fn hover_function_return_annotation_with_tuple() {
533 assert_hover!(
534 "
535/// Exciting documentation
536/// Maybe even multiple lines
537fn append(x: String, y: String) -> #(String, String) {
538 #(x, y)
539}
540",
541 find_position_of("String").under_char('r').nth_occurrence(3)
542 );
543}
544
545#[test]
546fn hover_module_constant_annotation() {
547 assert_hover!(
548 "
549/// Exciting documentation
550/// Maybe even multiple lines
551const one: Int = 1
552",
553 find_position_of("Int").under_last_char()
554 );
555}
556
557#[test]
558fn hover_type_constructor_annotation() {
559 assert_hover!(
560 "
561type Wibble {
562 Wibble(arg: String)
563}
564",
565 find_position_of("String").under_char('n')
566 );
567}
568
569#[test]
570fn hover_type_alias_annotation() {
571 assert_hover!("type Wibble = Int", find_position_of("Int").under_char('n'));
572}
573
574#[test]
575fn hover_assignment_annotation() {
576 assert_hover!(
577 "
578fn wibble() {
579 let wobble: Int = 7
580 wobble
581}
582",
583 find_position_of("Int").under_last_char()
584 );
585}
586
587#[test]
588fn hover_function_arg_annotation_with_documentation() {
589 assert_hover!(
590 "
591/// Exciting documentation
592/// Maybe even multiple lines
593type Wibble {
594 Wibble(arg: String)
595}
596
597fn identity(x: Wibble) -> Wibble {
598 x
599}
600",
601 find_position_of("Wibble")
602 .under_last_char()
603 .nth_occurrence(3)
604 );
605}
606
607#[test]
608fn hover_import_unqualified_value() {
609 let code = "
610import example_module.{my_num}
611fn main() {
612 my_num
613}
614";
615
616 assert_hover!(
617 TestProject::for_source(code).add_module(
618 "example_module",
619 "
620/// Exciting documentation
621/// Maybe even multiple lines
622pub const my_num = 1"
623 ),
624 find_position_of("my_num").under_char('n')
625 );
626}
627
628#[test]
629fn hover_import_unqualified_value_from_hex() {
630 let code = "
631import example_module.{my_num}
632fn main() {
633 my_num
634}
635";
636
637 assert_hover!(
638 TestProject::for_source(code).add_hex_module(
639 "example_module",
640 "
641/// Exciting documentation
642/// Maybe even multiple lines
643pub const my_num = 1"
644 ),
645 find_position_of("my_num").under_char('n')
646 );
647}
648
649#[test]
650fn hover_import_unqualified_type() {
651 let code = "
652import example_module.{type MyType, MyType}
653fn main() -> MyType {
654 MyType
655}
656";
657
658 assert_hover!(
659 TestProject::for_source(code).add_module(
660 "example_module",
661 "
662/// Exciting documentation
663/// Maybe even multiple lines
664pub type MyType {
665 MyType
666}"
667 ),
668 find_position_of("MyType").under_last_char()
669 );
670}
671
672#[test]
673fn hover_works_even_for_invalid_code() {
674 assert_hover!(
675 "
676fn invalid() { 1 + Nil }
677fn valid() { Nil }
678",
679 find_position_of("fn valid").under_char('v')
680 );
681}
682
683#[test]
684fn hover_for_pattern_spread_ignoring_all_fields() {
685 assert_hover!(
686 "
687pub type Model {
688 Model(
689 Int,
690 Float,
691 label1: Int,
692 label2: String,
693 )
694}
695
696pub fn main() {
697 case todo {
698 Model(..) -> todo
699 }
700}
701",
702 find_position_of("..")
703 );
704}
705
706#[test]
707fn hover_for_pattern_spread_ignoring_some_fields() {
708 assert_hover!(
709 "
710pub type Model {
711 Model(
712 Int,
713 Float,
714 label1: Int,
715 label2: String,
716 )
717}
718
719pub fn main() {
720 case todo {
721 Model(_, label1: _, ..) -> todo
722 }
723}
724",
725 find_position_of("..").under_last_char()
726 );
727}
728
729#[test]
730fn hover_for_pattern_spread_ignoring_all_positional_fields() {
731 assert_hover!(
732 "
733pub type Model {
734 Model(
735 Int,
736 Float,
737 label1: Int,
738 label2: String,
739 )
740}
741
742pub fn main() {
743 case todo {
744 Model(_, _, _, ..) -> todo
745 }
746}
747",
748 find_position_of("..")
749 );
750}
751
752#[test]
753fn hover_label_shorthand_in_call_arg() {
754 assert_hover!(
755 "
756fn wibble(arg1 arg1: Int, arg2 arg2: Bool) { Nil }
757
758fn main() {
759 let arg1 = 1
760 let arg2 = True
761 wibble(arg2:, arg1:)
762}
763",
764 find_position_of("arg2:").nth_occurrence(2)
765 );
766}
767
768#[test]
769fn hover_label_shorthand_in_pattern_call_arg() {
770 assert_hover!(
771 "
772pub type Wibble { Wibble(arg1: Int, arg2: Bool) }
773
774pub fn main() {
775 case todo {
776 Wibble(arg2:, ..) -> todo
777 }
778}
779",
780 find_position_of("arg2:")
781 .nth_occurrence(2)
782 .under_last_char()
783 );
784}
785
786#[test]
787fn hover_label_shorthand_in_pattern_call_arg_2() {
788 assert_hover!(
789 "
790pub type Wibble { Wibble(arg1: Int, arg2: Bool) }
791
792pub fn main() {
793 let Wibble(arg2:, ..) = todo
794}
795",
796 find_position_of("arg2:").nth_occurrence(2).under_char('r')
797 );
798}
799
800#[test]
801fn hover_contextual_type() {
802 let code = "
803import wibble/wobble
804const value = wobble.Wobble
805";
806
807 assert_hover!(
808 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"),
809 find_position_of("value").under_char('v')
810 );
811}
812
813#[test]
814fn hover_contextual_type_aliased_module() {
815 let code = "
816import wibble/wobble as wubble
817const value = wubble.Wobble
818";
819
820 assert_hover!(
821 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"),
822 find_position_of("value").under_char('v')
823 );
824}
825
826#[test]
827fn hover_contextual_type_unqualified() {
828 let code = "
829import wibble/wobble.{type Wibble}
830const value = wobble.Wobble
831";
832
833 assert_hover!(
834 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"),
835 find_position_of("value").under_char('v')
836 );
837}
838
839#[test]
840fn hover_contextual_type_unqualified_aliased() {
841 let code = "
842import wibble/wobble.{type Wibble as Wobble}
843const value = wobble.Wobble
844";
845
846 assert_hover!(
847 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"),
848 find_position_of("value").under_char('v')
849 );
850}
851
852#[test]
853fn hover_contextual_type_aliased() {
854 let code = "
855import wibble/wobble
856type Local = wobble.Wibble
857const value = wobble.Wobble
858";
859
860 assert_hover!(
861 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"),
862 find_position_of("value").under_char('v')
863 );
864}
865
866#[test]
867fn hover_contextual_type_function() {
868 let code = "
869import wibble/wobble
870type MyInt = Int
871fn func(value: wobble.Wibble) -> MyInt { 1 }
872";
873
874 assert_hover!(
875 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"),
876 find_position_of("func").under_char('f')
877 );
878}
879
880#[test]
881fn hover_contextual_type_unqualified_import() {
882 let code = "
883import wibble/wobble.{type Wibble as Wobble, Wobble}
884";
885
886 assert_hover!(
887 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"),
888 find_position_of("Wobble}").under_char('W')
889 );
890}
891
892#[test]
893fn hover_contextual_type_pattern() {
894 let code = "
895import wibble/wobble.{Wibble, Wobble, Wubble}
896
897pub fn cycle(wibble: wobble.Wibble) {
898 case wibble {
899 Wibble -> Wobble
900 Wobble -> Wubble
901 Wubble -> Wibble
902 }
903}
904";
905
906 assert_hover!(
907 TestProject::for_source(code)
908 .add_hex_module("wibble/wobble", "pub type Wibble { Wibble Wobble Wubble }"),
909 find_position_of("Wubble ->").under_char('u')
910 );
911}
912
913#[test]
914fn hover_contextual_type_pattern_spread() {
915 let code = "
916import wibble/wobble.{type Wibble as Wobble}
917
918type Thing {
919 Thing(id: Int, value: Wobble)
920}
921
922pub fn main(thing: Thing) {
923 case thing {
924 Thing(id: 0, ..) -> 12
925 _ -> 14
926 }
927}
928";
929
930 assert_hover!(
931 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
932 find_position_of("..").under_char('.')
933 );
934}
935
936#[test]
937fn hover_contextual_type_expression() {
938 let code = "
939import wibble/wobble
940
941pub fn main() {
942 let wibble = wobble.Wibble
943}
944";
945
946 assert_hover!(
947 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
948 find_position_of(".Wibble").under_char('l')
949 );
950}
951
952#[test]
953fn hover_contextual_type_arg() {
954 let code = "
955import wibble/wobble
956
957fn do_things(wibble: wobble.Wibble) { wibble }
958";
959
960 assert_hover!(
961 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
962 find_position_of("wibble:").under_char('w')
963 );
964}
965
966#[test]
967fn hover_print_type_variable_names() {
968 let code = "
969fn main(value: Result(ok, error)) {
970 let v = value
971 v
972}
973";
974
975 assert_hover!(
976 TestProject::for_source(code),
977 find_position_of("let v").under_char('v')
978 );
979}
980
981#[test]
982fn hover_print_unbound_type_variable_names() {
983 let code = "
984fn make_ok(value: some_type) {
985 let result = Ok(value)
986 result
987}
988";
989
990 assert_hover!(
991 TestProject::for_source(code),
992 find_position_of("result =").under_char('s')
993 );
994}
995
996#[test]
997fn hover_print_unbound_type_variable_name_without_conflicts() {
998 let code = "
999fn make_ok(value: a) {
1000 let result = Ok(value)
1001 result
1002}
1003";
1004
1005 assert_hover!(
1006 TestProject::for_source(code),
1007 find_position_of("result =").under_char('s')
1008 );
1009}
1010
1011#[test]
1012fn hover_print_imported_alias() {
1013 let code = "
1014import aliases.{type Aliased}
1015const thing: Aliased = 10
1016";
1017
1018 assert_hover!(
1019 TestProject::for_source(code).add_hex_module("aliases", "pub type Aliased = Int"),
1020 find_position_of("thing").under_char('g')
1021 );
1022}
1023
1024#[test]
1025fn hover_prelude_type() {
1026 let code = "
1027const number = 100
1028";
1029
1030 assert_hover!(
1031 TestProject::for_source(code),
1032 find_position_of("number").under_char('b')
1033 );
1034}
1035
1036#[test]
1037fn hover_shadowed_prelude_type() {
1038 let code = "
1039type Int { Int }
1040const number = 100
1041";
1042
1043 assert_hover!(
1044 TestProject::for_source(code),
1045 find_position_of("number").under_char('b')
1046 );
1047}
1048
1049#[test]
1050fn hover_shadowed_prelude_type_imported() {
1051 let code = "
1052import numbers.{type Int}
1053const number = 100
1054";
1055
1056 assert_hover!(
1057 TestProject::for_source(code).add_hex_module("numbers", "pub type Int"),
1058 find_position_of("number =").under_char('b')
1059 );
1060}
1061
1062#[test]
1063fn hover_contextual_type_annotation() {
1064 let code = "
1065import wibble/wobble
1066
1067fn make_wibble() -> wobble.Wibble { wobble.Wibble }
1068";
1069
1070 assert_hover!(
1071 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
1072 find_position_of("-> wobble.Wibble").under_char('i')
1073 );
1074}
1075
1076#[test]
1077fn hover_contextual_type_annotation_prelude() {
1078 let code = "
1079fn add_one(a: Int) -> Int {
1080 a + 1
1081}
1082";
1083
1084 assert_hover!(
1085 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
1086 find_position_of("-> Int").under_char('I')
1087 );
1088}
1089
1090#[test]
1091fn hover_contextual_type_annotation_unqualified() {
1092 let code = "
1093import wibble/wobble.{type Wibble}
1094
1095fn main(wibble: Wibble) {
1096 wibble
1097}
1098";
1099
1100 assert_hover!(
1101 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
1102 find_position_of(": Wibble").under_char('W')
1103 );
1104}
1105
1106#[test]
1107fn hover_contextual_type_annotation_unqualified_aliased() {
1108 let code = "
1109import wibble/wobble.{type Wibble as Wubble}
1110
1111fn main(wibble: Wubble) {
1112 wibble
1113}
1114";
1115
1116 assert_hover!(
1117 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
1118 find_position_of(": Wubble").under_char('W')
1119 );
1120}
1121
1122#[test]
1123fn hover_contextual_type_annotation_aliased_module() {
1124 let code = "
1125import wibble/wobble as wubble
1126
1127fn main(wibble: wubble.Wibble) {
1128 wibble
1129}
1130";
1131
1132 assert_hover!(
1133 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
1134 find_position_of(": wubble.Wibble").under_char('W')
1135 );
1136}
1137
1138#[test]
1139fn hover_contextual_type_annotation_aliased() {
1140 let code = "
1141import wibble/wobble
1142
1143type Wubble = wobble.Wibble
1144
1145fn main(wibble: Wubble) {
1146 wibble
1147}
1148";
1149
1150 assert_hover!(
1151 TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"),
1152 find_position_of(": Wubble").under_char('e')
1153 );
1154}
1155
1156#[test]
1157fn hover_print_underlying_for_alias_with_parameters() {
1158 let code = "
1159type LocalResult = Result(String, Int)
1160
1161fn do_thing() -> LocalResult {
1162 Error(1)
1163}
1164";
1165
1166 assert_hover!(
1167 TestProject::for_source(code),
1168 find_position_of("do_thing").under_char('d')
1169 );
1170}
1171
1172#[test]
1173fn hover_print_alias_when_parameters_match() {
1174 let code = "
1175type MyResult(a, b) = Result(a, b)
1176
1177fn do_thing() -> MyResult(Int, Int) {
1178 Error(1)
1179}
1180";
1181
1182 assert_hover!(
1183 TestProject::for_source(code),
1184 find_position_of("do_thing").under_char('d')
1185 );
1186}
1187
1188#[test]
1189fn hover_print_underlying_for_imported_alias() {
1190 let code = "
1191import alias.{type A}
1192
1193fn wibble() -> Result(Int, String) {
1194 todo
1195}
1196";
1197
1198 assert_hover!(
1199 TestProject::for_source(code).add_hex_module("alias", "pub type A = Result(Int, String)"),
1200 find_position_of("wibble").under_char('l')
1201 );
1202}
1203
1204#[test]
1205fn hover_print_aliased_imported_generic_type() {
1206 let code = "
1207import gleam/option.{type Option as Maybe}
1208
1209const none: Maybe(Int) = option.None
1210";
1211
1212 assert_hover!(
1213 TestProject::for_source(code)
1214 .add_hex_module("gleam/option", "pub type Option(a) { None Some(a) }"),
1215 find_position_of("none").under_char('e')
1216 );
1217}
1218
1219#[test]
1220fn hover_print_qualified_prelude_type_when_shadowed_by_alias() {
1221 let code = "
1222type Result = #(Bool, String)
1223const ok = Ok(10)
1224";
1225
1226 assert_hover!(
1227 TestProject::for_source(code),
1228 find_position_of("ok").under_char('k')
1229 );
1230}
1231
1232#[test]
1233fn hover_print_qualified_prelude_type_when_shadowed_by_imported_alias() {
1234 let code = "
1235import alias.{type Bool}
1236const value = True
1237";
1238
1239 assert_hover!(
1240 TestProject::for_source(code).add_hex_module("alias", "pub type Bool = #(Int, Int)"),
1241 find_position_of("value").under_char('v')
1242 );
1243}
1244
1245// https://github.com/gleam-lang/gleam/issues/3761
1246#[test]
1247fn hover_over_block_in_list_spread() {
1248 let code = "
1249pub fn main() {
1250 [1, 2, ..{
1251 let x = 1
1252 [x]
1253 }]
1254}
1255";
1256
1257 assert_hover!(TestProject::for_source(code), find_position_of("x"));
1258}
1259
1260// https://github.com/gleam-lang/gleam/issues/3758
1261#[test]
1262fn hover_for_anonymous_function_annotation() {
1263 let code = "
1264/// An example type.
1265pub type Wibble
1266
1267pub fn main() {
1268 fn(w: Wibble) { todo }
1269}
1270";
1271
1272 assert_hover!(
1273 TestProject::for_source(code),
1274 find_position_of("w: Wibble").under_char('b')
1275 );
1276}
1277
1278#[test]
1279fn hover_for_label_in_pattern() {
1280 let code = "
1281type Wibble {
1282 Wibble(wibble: Int, wobble: Int)
1283}
1284
1285pub fn main() {
1286 let Wibble(wibble: _, wobble: _) = todo
1287 todo
1288}
1289";
1290
1291 assert_hover!(
1292 TestProject::for_source(code),
1293 find_position_of("wibble: _").under_char('l')
1294 );
1295}
1296
1297#[test]
1298fn hover_for_label_in_expression() {
1299 let code = "
1300fn add(wibble a, wobble b) {
1301 a + b
1302}
1303
1304pub fn main() {
1305 add(wibble: 1, wobble: 2)
1306}
1307";
1308
1309 assert_hover!(
1310 TestProject::for_source(code),
1311 find_position_of("wibble:").under_char('i')
1312 );
1313}
1314
1315#[test]
1316fn hover_for_pattern_in_use() {
1317 let code = "
1318type Wibble {
1319 Wibble(Int, Float)
1320}
1321
1322pub fn main() {
1323 use Wibble(int, float) <- todo
1324 todo
1325}
1326";
1327
1328 assert_hover!(
1329 TestProject::for_source(code),
1330 find_position_of("int").under_char('i')
1331 );
1332}
1333
1334#[test]
1335fn hover_for_annotation_in_use() {
1336 let code = "
1337pub fn main() {
1338 use something: Int <- todo
1339 todo
1340}
1341";
1342
1343 assert_hover!(
1344 TestProject::for_source(code),
1345 find_position_of("Int").under_char('n')
1346 );
1347}
1348
1349#[test]
1350fn hover_on_pipe_with_invalid_step() {
1351 assert_hover!(
1352 "
1353pub fn main() {
1354 [1, 2, 3]
1355 |> map(wibble)
1356 |> filter(fn(value) { value })
1357}
1358
1359fn map(list: List(a), fun: fn(a) -> b) -> List(b) {}
1360fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}
1361",
1362 find_position_of("[")
1363 );
1364}
1365
1366#[test]
1367fn hover_on_pipe_with_invalid_step_1() {
1368 assert_hover!(
1369 "
1370pub fn main() {
1371 [1, 2, 3]
1372 |> map(wibble)
1373 |> filter(fn(value) { value })
1374}
1375
1376fn map(list: List(a), fun: fn(a) -> b) -> List(b) {}
1377fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}
1378",
1379 find_position_of("1")
1380 );
1381}
1382
1383#[test]
1384fn hover_on_pipe_with_invalid_step_2() {
1385 assert_hover!(
1386 "
1387pub fn main() {
1388 [1, 2, 3]
1389 |> map(wibble)
1390 |> filter(fn(value) { value })
1391}
1392
1393fn map(list: List(a), fun: fn(a) -> b) -> List(b) {}
1394fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}
1395",
1396 find_position_of("map")
1397 );
1398}
1399
1400#[test]
1401fn hover_on_pipe_with_invalid_step_3() {
1402 assert_hover!(
1403 "
1404pub fn main() {
1405 [1, 2, 3]
1406 |> map(wibble)
1407 |> filter(fn(value) { value })
1408}
1409
1410fn map(list: List(a), fun: fn(a) -> b) -> List(b) {}
1411fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}
1412",
1413 find_position_of("wibble")
1414 );
1415}
1416
1417#[test]
1418fn hover_on_pipe_with_invalid_step_4() {
1419 assert_hover!(
1420 "
1421pub fn main() {
1422 [1, 2, 3]
1423 |> map(wibble)
1424 |> filter(fn(value) { value })
1425}
1426
1427fn map(list: List(a), fun: fn(a) -> b) -> List(b) {}
1428fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}
1429",
1430 find_position_of("filter")
1431 );
1432}
1433
1434#[test]
1435fn hover_on_pipe_with_invalid_step_5() {
1436 assert_hover!(
1437 "
1438pub fn main() {
1439 [1, 2, 3]
1440 |> map(wibble)
1441 |> filter(fn(value) { value })
1442}
1443
1444fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo }
1445fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo }
1446",
1447 find_position_of("fn(value)")
1448 );
1449}
1450
1451#[test]
1452fn hover_on_pipe_with_invalid_step_6() {
1453 assert_hover!(
1454 "
1455pub fn main() {
1456 [1, 2, 3]
1457 |> wibble
1458 |> filter(fn(value) { value })
1459}
1460
1461fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo }
1462",
1463 find_position_of("wibble")
1464 );
1465}
1466
1467#[test]
1468fn hover_on_pipe_with_invalid_step_8() {
1469 assert_hover!(
1470 "
1471pub fn main() {
1472 [1, 2, 3]
1473 |> wibble
1474 |> filter(fn(value) { value })
1475}
1476
1477fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo }
1478",
1479 find_position_of("fn(value)")
1480 );
1481}
1482
1483#[test]
1484fn hover_over_module_name() {
1485 let src = "
1486import wibble
1487
1488pub fn main() {
1489 wibble.wibble()
1490}
1491";
1492 assert_hover!(
1493 TestProject::for_source(src).add_hex_module(
1494 "wibble",
1495 "
1496//// This is the wibble module.
1497//// Here is some documentation about it.
1498//// This module does stuff
1499
1500pub fn wibble() {
1501 todo
1502}
1503"
1504 ),
1505 find_position_of("wibble.")
1506 );
1507}
1508
1509#[test]
1510fn hover_over_module_with_path() {
1511 let src = "
1512import wibble/wobble
1513
1514pub fn main() {
1515 wobble.wibble()
1516}
1517";
1518 assert_hover!(
1519 TestProject::for_source(src).add_hex_module(
1520 "wibble/wobble",
1521 "
1522//// The module documentation
1523
1524pub fn wibble() {
1525 todo
1526}
1527"
1528 ),
1529 find_position_of("wobble.")
1530 );
1531}
1532
1533#[test]
1534fn hover_over_module_name_in_annotation() {
1535 let src = "
1536import wibble
1537
1538pub fn main(w: wibble.Wibble) {
1539 todo
1540}
1541";
1542 assert_hover!(
1543 TestProject::for_source(src).add_hex_module(
1544 "wibble",
1545 "
1546//// This is the wibble module.
1547//// Here is some documentation about it.
1548//// This module does stuff
1549
1550pub type Wibble
1551"
1552 ),
1553 find_position_of("wibble.")
1554 );
1555}
1556
1557#[test]
1558fn hover_over_imported_module() {
1559 let src = "
1560import wibble
1561";
1562 assert_hover!(
1563 TestProject::for_source(src).add_hex_module(
1564 "wibble",
1565 "
1566//// This is the wibble module.
1567//// Here is some documentation about it.
1568//// This module does stuff
1569"
1570 ),
1571 find_position_of("wibble")
1572 );
1573}
1574
1575#[test]
1576fn no_hexdocs_link_when_hovering_over_local_module() {
1577 let src = "
1578import wibble
1579";
1580 assert_hover!(
1581 TestProject::for_source(src).add_module(
1582 "wibble",
1583 "
1584//// This is the wibble module.
1585//// Here is some documentation about it.
1586//// This module does stuff
1587"
1588 ),
1589 find_position_of("wibble")
1590 );
1591}
1592
1593#[test]
1594fn hover_for_constant_int() {
1595 assert_hover!(
1596 "
1597const ten = 10
1598",
1599 find_position_of("10")
1600 );
1601}
1602
1603#[test]
1604fn hover_for_constant_float() {
1605 assert_hover!(
1606 "
1607const pi = 3.14
1608",
1609 find_position_of("3.14")
1610 );
1611}
1612#[test]
1613fn hover_for_constant_string() {
1614 assert_hover!(
1615 r#"
1616const message = "Hello!"
1617"#,
1618 find_position_of("!")
1619 );
1620}
1621
1622#[test]
1623fn hover_for_constant_other_constant() {
1624 assert_hover!(
1625 "
1626const constant1 = 10
1627const constant2 = constant1
1628",
1629 find_position_of("= constant1").under_char('s')
1630 );
1631}
1632
1633#[test]
1634fn hover_for_constant_record() {
1635 assert_hover!(
1636 "
1637type Wibble {
1638 Wibble(Int)
1639}
1640
1641const w = Wibble(10)
1642",
1643 find_position_of("Wibble(10)").under_char('i')
1644 );
1645}
1646
1647#[test]
1648fn hover_for_constant_tuple() {
1649 assert_hover!(
1650 "
1651const tuple = #(1, 3.5, False)
1652",
1653 find_position_of("#(")
1654 );
1655}
1656
1657#[test]
1658fn hover_for_constant_tuple_element() {
1659 assert_hover!(
1660 "
1661const tuple = #(1, 3.5, False)
1662",
1663 find_position_of("False")
1664 );
1665}
1666
1667#[test]
1668fn hover_for_constant_list() {
1669 assert_hover!(
1670 "
1671const numbers = [2, 4, 6, 8]
1672",
1673 find_position_of("[")
1674 );
1675}
1676
1677#[test]
1678fn hover_for_constant_list_element() {
1679 assert_hover!(
1680 "
1681const numbers = [2, 4, 6, 8]
1682",
1683 find_position_of("4")
1684 );
1685}
1686
1687#[test]
1688fn hover_for_constant_string_concatenation() {
1689 assert_hover!(
1690 r#"
1691const name = "Bob"
1692const message = "Hello " <> name
1693"#,
1694 find_position_of("<>")
1695 );
1696}
1697
1698#[test]
1699fn hover_for_constant_string_concatenation_side() {
1700 assert_hover!(
1701 r#"
1702const name = "Bob"
1703const message = "Hello " <> name
1704"#,
1705 find_position_of("<> name").under_char('n')
1706 );
1707}
1708
1709#[test]
1710fn hover_for_constant_bit_array() {
1711 assert_hover!(
1712 "
1713const bits = <<1:2, 3:4>>
1714",
1715 find_position_of(",")
1716 );
1717}
1718
1719#[test]
1720fn hover_for_constant_bit_array_segment() {
1721 assert_hover!(
1722 "
1723const bits = <<1:2, 3:4>>
1724",
1725 find_position_of("1")
1726 );
1727}
1728
1729#[test]
1730fn hover_for_constant_bit_array_segment_option() {
1731 assert_hover!(
1732 "
1733const bits = <<1:size(2), 3:4>>
1734",
1735 find_position_of("2")
1736 );
1737}
1738
1739#[test]
1740fn hover_for_nested_constant() {
1741 assert_hover!(
1742 "
1743type Wibble {
1744 Wibble
1745 Wobble(BitArray)
1746}
1747
1748const value = #(1, 2, [Wibble, Wobble(<<1, 2, 3>>), Wibble])
1749",
1750 find_position_of("3")
1751 );
1752}