Skip to content

Commit 3bd8874

Browse files
committed
Show completable item in code fence
1 parent efc9626 commit 3bd8874

8 files changed

+193
-16
lines changed

analysis/bin/main.ml

+3-3
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ let main () =
129129
| _ -> print_endline "\"ERR: Did not find root \"")
130130
| [_; "completion"; path; line; col; currentFile] ->
131131
printHeaderInfo path line col;
132-
Commands.completion ~debug ~path
132+
Commands.completion debug ~path
133133
~pos:(int_of_string line, int_of_string col)
134134
~currentFile
135135
| [_; "completionResolve"; path; modulePath] ->
@@ -208,10 +208,10 @@ let main () =
208208
(Json.escape (CreateInterface.command ~path ~cmiFile))
209209
| [_; "format"; path] ->
210210
Printf.printf "\"%s\"" (Json.escape (Commands.format ~path))
211-
| [_; "test"; path] -> Commands.test ~path
211+
| [_; "test"; path] -> Commands.test ~path ~debug
212212
| [_; "test_revamped"; path; config_file_path] ->
213213
Packages.overrideConfigFilePath := Some config_file_path;
214-
Commands.test ~path
214+
Commands.test ~path ~debug
215215
| args when List.mem "-h" args || List.mem "--help" args -> prerr_endline help
216216
| _ ->
217217
prerr_endline help;

analysis/src/CodeFence.ml

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
(* Define a type for a range with start and finish indices *)
2+
type range = {start: int; finish: int}
3+
4+
(* --- Helper function to find the 0-based line index containing a given 0-based character index --- *)
5+
let get_line_index_from_char_index code char_index =
6+
let lines = String.split_on_char '\n' code in
7+
let rec find_line_idx current_char_idx current_line_num remaining_lines =
8+
match remaining_lines with
9+
| [] ->
10+
max 0 (current_line_num - 1)
11+
(* If char_index is beyond the end, return last line index *)
12+
| line :: tl ->
13+
let line_length = String.length line in
14+
(* Check if char_index is within the current line (including the newline char) *)
15+
if
16+
char_index >= current_char_idx
17+
&& char_index <= current_char_idx + line_length
18+
then current_line_num
19+
else
20+
(* Move to the next line, account for the newline character (+1) *)
21+
find_line_idx
22+
(current_char_idx + line_length + 1)
23+
(current_line_num + 1) tl
24+
in
25+
find_line_idx 0 0 lines
26+
27+
(* --- Helper function to calculate the 0-based character index of the start of a given 0-based line index --- *)
28+
let get_char_index_from_line_index code target_line_index =
29+
let lines = String.split_on_char '\n' code in
30+
let rec calculate_start_index_impl current_char_idx current_line_num
31+
lines_to_process =
32+
if current_line_num >= target_line_index then current_char_idx
33+
else
34+
match lines_to_process with
35+
| [] -> current_char_idx (* Target line index is out of bounds *)
36+
| line :: tl ->
37+
(* Move past the current line and its newline character *)
38+
calculate_start_index_impl
39+
(current_char_idx + String.length line + 1)
40+
(current_line_num + 1) tl
41+
in
42+
calculate_start_index_impl 0 0 lines
43+
44+
(* --- Main formatting function --- *)
45+
let format_code_snippet_cropped code (underline_range : range option)
46+
lines_around_annotation =
47+
let lines = String.split_on_char '\n' code in
48+
let total_lines = List.length lines in
49+
let formatted_output = Buffer.create (String.length code) in
50+
(* Initial capacity *)
51+
52+
(* Determine the central line index for cropping *)
53+
let target_line_index =
54+
match underline_range with
55+
| Some {start; finish = _} -> get_line_index_from_char_index code start
56+
| None -> 0 (* Default to first line if no annotations *)
57+
in
58+
59+
(* Determine the cropping window (0-based line indices) *)
60+
let start_line_index = max 0 (target_line_index - lines_around_annotation) in
61+
let end_line_index =
62+
min (total_lines - 1) (target_line_index + lines_around_annotation)
63+
in
64+
65+
(* Keep track of the global character index corresponding to the start of the *current* line being iterated over *)
66+
let current_char_index = ref 0 in
67+
68+
(* Iterate through all original lines to correctly track current_char_index *)
69+
List.iteri
70+
(fun original_line_idx line ->
71+
let line_length = String.length line in
72+
(* Check if the current original line is within our cropping window *)
73+
if
74+
original_line_idx >= start_line_index
75+
&& original_line_idx <= end_line_index
76+
then (
77+
let original_line_number = original_line_idx + 1 in
78+
(* 1-based for display *)
79+
let line_number_prefix = Printf.sprintf "%d + " original_line_number in
80+
let prefix_length = String.length line_number_prefix in
81+
82+
(* Add the code line *)
83+
Buffer.add_string formatted_output line_number_prefix;
84+
Buffer.add_string formatted_output line;
85+
Buffer.add_char formatted_output '\n';
86+
87+
(* Prepare the annotation line buffer *)
88+
let annotation_line_buffer =
89+
Buffer.create (prefix_length + line_length)
90+
in
91+
Buffer.add_string annotation_line_buffer (String.make prefix_length ' ');
92+
93+
(* Initial padding *)
94+
let has_annotation_on_this_line = ref false in
95+
96+
(* Check each character position within this line for annotations *)
97+
for i = 0 to line_length - 1 do
98+
let global_char_index = !current_char_index + i in
99+
let annotation_char = ref ' ' in
100+
(* Default to space *)
101+
102+
(* Check for underline using Option.iter *)
103+
Option.iter
104+
(fun {start; finish} ->
105+
if global_char_index >= start && global_char_index < finish then (
106+
annotation_char := '-' (* '¯' *);
107+
(* Macron symbol *)
108+
has_annotation_on_this_line := true))
109+
underline_range;
110+
111+
Buffer.add_char annotation_line_buffer !annotation_char
112+
done;
113+
114+
(* Add the annotation line to the main output if needed *)
115+
if !has_annotation_on_this_line then (
116+
Buffer.add_buffer formatted_output annotation_line_buffer;
117+
Buffer.add_char formatted_output '\n'));
118+
119+
(* Update the global character index to the start of the next line *)
120+
(* This happens regardless of whether the line was in the cropped window *)
121+
current_char_index := !current_char_index + line_length + 1
122+
(* +1 for the newline *))
123+
lines;
124+
125+
Buffer.contents formatted_output

analysis/src/Commands.ml

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
let completion ~debug ~path ~pos ~currentFile =
1+
let completion ?(source = None) (debug : bool) ~path ~pos ~currentFile =
22
let completions =
33
match
4-
Completions.getCompletions ~debug ~path ~pos ~currentFile ~forHover:false
4+
Completions.getCompletions ~source debug ~path ~pos ~currentFile
5+
~forHover:false
56
with
67
| None -> []
78
| Some (completions, full, _) ->
@@ -293,7 +294,7 @@ let format ~path =
293294
let diagnosticSyntax ~path =
294295
print_endline (Diagnostics.document_syntax ~path |> Protocol.array)
295296

296-
let test ~path =
297+
let test ~path ~debug =
297298
Uri.stripPath := true;
298299
match Files.readFile path with
299300
| None -> assert false
@@ -362,7 +363,8 @@ let test ~path =
362363
("Complete " ^ path ^ " " ^ string_of_int line ^ ":"
363364
^ string_of_int col);
364365
let currentFile = createCurrentFile () in
365-
completion ~debug:true ~path ~pos:(line, col) ~currentFile;
366+
let source = Files.readFile currentFile in
367+
completion ~source debug ~path ~pos:(line, col) ~currentFile;
366368
Sys.remove currentFile
367369
| "cre" ->
368370
let modulePath = String.sub rest 3 (String.length rest - 3) in

analysis/src/Completions.ml

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
let getCompletions ~debug ~path ~pos ~currentFile ~forHover =
1+
let getCompletions ?(source = None) (debug : bool) ~path ~pos ~currentFile
2+
~forHover =
23
ignore forHover;
34
let textOpt = Files.readFile currentFile in
45
match textOpt with
@@ -8,8 +9,32 @@ let getCompletions ~debug ~path ~pos ~currentFile ~forHover =
89
CompletionFrontEndRevamped.completionWithParser ~debug ~path
910
~posCursor:pos ~currentFile ~text
1011
with
11-
| None -> None
12+
| None ->
13+
source
14+
|> Option.iter (fun _ ->
15+
print_endline "Completion Frontend did not return completable");
16+
None
1217
| Some (completable, scope) -> (
18+
let _ =
19+
match source with
20+
| Some text -> (
21+
match SharedTypes.CompletableRevamped.try_loc completable with
22+
| Some loc ->
23+
let range =
24+
CodeFence.
25+
{
26+
start = loc.Location.loc_start.pos_cnum;
27+
finish = loc.Warnings.loc_end.pos_cnum;
28+
}
29+
in
30+
Printf.printf "Found Completable: %s\n\n"
31+
(SharedTypes.CompletableRevamped.toString completable);
32+
CodeFence.format_code_snippet_cropped text (Some range) 3
33+
|> print_endline
34+
| None -> ())
35+
| None -> ()
36+
in
37+
1338
(* Only perform expensive ast operations if there are completables *)
1439
match Cmt.loadFullCmtFromPath ~path with
1540
| None -> None

analysis/src/Hover.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ let hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ =
150150
makes it (most often) work with unsaved content. *)
151151
let getHoverViaCompletions ~debug ~path ~pos ~currentFile ~forHover
152152
~supportsMarkdownLinks =
153-
match Completions.getCompletions ~debug ~path ~pos ~currentFile ~forHover with
153+
match Completions.getCompletions debug ~path ~pos ~currentFile ~forHover with
154154
| None -> None
155155
| Some (completions, ({file; package} as full), scope) -> (
156156
let rawOpens = Scope.getRawOpens scope in

analysis/src/SharedTypes.ml

+15
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,21 @@ module CompletableRevamped = struct
793793
| CextensionNode of string
794794
| Cdecorator of string
795795
| CdecoratorPayload of decoratorPayload
796+
797+
let toString (t : t) =
798+
match t with
799+
| Cexpression _ -> "Cexpression"
800+
| Cpattern _ -> "Cpattern"
801+
| Cnone -> "Cnone"
802+
| CextensionNode _ -> "CextensionNode"
803+
| Cdecorator _ -> "Cdecorator"
804+
| CdecoratorPayload _ -> "CdecoratorPayload"
805+
806+
let try_loc (t : t) =
807+
match t with
808+
| Cexpression {typeLoc; _} -> Some typeLoc
809+
| Cpattern {typeLoc; _} -> Some typeLoc
810+
| _ -> None
796811
end
797812

798813
module ScopeTypes = struct

tests/analysis_new_tests/tests/test_files/__snapshots__/RecordFieldCompletions.res_Record field completion in nested record, another level.snap

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
Complete /Users/nojaf/Projects/rescript/tests/analysis_new_tests/tests/test_files/.build/RecordFieldCompletions_2.res 1:45
2-
posCursor:[1:45] posNoWhite:[1:44] Found expr:[1:8->1:45]
3-
Package opens Stdlib.place holder Pervasives.JsxModules.place holder
4-
Resolved opens 1 Stdlib
2+
Found Completable: Cexpression
3+
4+
1 + // Record field completion in nested record, another level
5+
2 + let x = TestTypeDefs.nestedTestRecord.nested.
6+
------------------------------------
7+
3 + // ^com
8+
4 +
9+
510
[{
611
"label": "name",
712
"kind": 5,

tests/analysis_new_tests/tests/test_files/__snapshots__/RecordFieldCompletions.res_Record field completion in nested record.snap

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
Complete /Users/nojaf/Projects/rescript/tests/analysis_new_tests/tests/test_files/.build/RecordFieldCompletions_1.res 1:38
2-
posCursor:[1:38] posNoWhite:[1:37] Found expr:[1:8->1:38]
3-
Package opens Stdlib.place holder Pervasives.JsxModules.place holder
4-
Resolved opens 1 Stdlib
2+
Found Completable: Cexpression
3+
4+
1 + // Record field completion in nested record
5+
2 + let x = TestTypeDefs.nestedTestRecord.
6+
-----------------------------
7+
3 + // ^com
8+
4 +
9+
510
[{
611
"label": "test",
712
"kind": 5,

0 commit comments

Comments
 (0)