Skip to content

Commit 7d3c1c8

Browse files
committed
Show completable item in code fence
1 parent 0b7dcaa commit 7d3c1c8

8 files changed

+200
-21
lines changed

analysis/bin/main.ml

+5-4
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,12 @@ let main () =
130130
| [_; "completion"; path; line; col; currentFile] ->
131131
printHeaderInfo path line col;
132132
if !Cfg.useRevampedCompletion then
133-
Commands.completionRevamped ~debug ~path
133+
let source = Files.readFile currentFile in
134+
Commands.completionRevamped ~source ~debug ~path
134135
~pos:(int_of_string line, int_of_string col)
135136
~currentFile
136137
else
137-
Commands.completion ~debug ~path
138+
Commands.completion debug ~path
138139
~pos:(int_of_string line, int_of_string col)
139140
~currentFile
140141
| [_; "completionResolve"; path; modulePath] ->
@@ -213,11 +214,11 @@ let main () =
213214
(Json.escape (CreateInterface.command ~path ~cmiFile))
214215
| [_; "format"; path] ->
215216
Printf.printf "\"%s\"" (Json.escape (Commands.format ~path))
216-
| [_; "test"; path] -> Commands.test ~path
217+
| [_; "test"; path] -> Commands.test ~path ~debug
217218
| [_; "test_revamped"; path; config_file_path] ->
218219
Packages.overrideConfigFilePath := Some config_file_path;
219220
Cfg.useRevampedCompletion := true;
220-
Commands.test ~path
221+
Commands.test ~path ~debug
221222
| args when List.mem "-h" args || List.mem "--help" args -> prerr_endline help
222223
| _ ->
223224
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

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
let completion ~debug ~path ~pos ~currentFile =
1+
let completion (debug : bool) ~path ~pos ~currentFile =
22
let completions =
33
match
4-
Completions.getCompletions ~debug ~path ~pos ~currentFile ~forHover:false
4+
Completions.getCompletions debug ~path ~pos ~currentFile ~forHover:false
55
with
66
| None -> []
77
| Some (completions, full, _) ->
@@ -11,9 +11,11 @@ let completion ~debug ~path ~pos ~currentFile =
1111
in
1212
completions |> Protocol.array |> print_endline
1313

14-
let completionRevamped ~debug ~path ~pos ~currentFile =
14+
let completionRevamped ?(source = None) ~debug ~path ~pos ~currentFile =
1515
let completions =
16-
match Completions.getCompletionsRevamped ~debug ~path ~pos ~currentFile with
16+
match
17+
Completions.getCompletionsRevamped ~source ~debug ~path ~pos ~currentFile
18+
with
1719
| None -> []
1820
| Some (completions, full, _) ->
1921
completions
@@ -313,7 +315,7 @@ let format ~path =
313315
let diagnosticSyntax ~path =
314316
print_endline (Diagnostics.document_syntax ~path |> Protocol.array)
315317

316-
let test ~path =
318+
let test ~path ~debug =
317319
Uri.stripPath := true;
318320
match Files.readFile path with
319321
| None -> assert false
@@ -383,8 +385,10 @@ let test ~path =
383385
^ string_of_int col);
384386
let currentFile = createCurrentFile () in
385387
if !Cfg.useRevampedCompletion then
386-
completionRevamped ~debug:true ~path ~pos:(line, col) ~currentFile
387-
else completion ~debug:true ~path ~pos:(line, col) ~currentFile;
388+
let source = Files.readFile currentFile in
389+
completionRevamped ~source ~debug:true ~path ~pos:(line, col)
390+
~currentFile
391+
else completion debug ~path ~pos:(line, col) ~currentFile;
388392
Sys.remove currentFile
389393
| "cre" ->
390394
let modulePath = String.sub rest 3 (String.length rest - 3) in

analysis/src/Completions.ml

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
let getCompletions ~debug ~path ~pos ~currentFile ~forHover =
1+
let getCompletions (debug : bool) ~path ~pos ~currentFile ~forHover =
22
let textOpt = Files.readFile currentFile in
33
match textOpt with
44
| None | Some "" -> None
@@ -21,7 +21,7 @@ let getCompletions ~debug ~path ~pos ~currentFile ~forHover =
2121
in
2222
Some (completables, full, scope)))
2323

24-
let getCompletionsRevamped ~debug ~path ~pos ~currentFile =
24+
let getCompletionsRevamped ?(source = None) ~debug ~path ~pos ~currentFile =
2525
let textOpt = Files.readFile currentFile in
2626
match textOpt with
2727
| None | Some "" -> None
@@ -30,8 +30,32 @@ let getCompletionsRevamped ~debug ~path ~pos ~currentFile =
3030
CompletionFrontEndRevamped.completionWithParser ~debug ~path
3131
~posCursor:pos ~currentFile ~text
3232
with
33-
| None -> None
33+
| None ->
34+
source
35+
|> Option.iter (fun _ ->
36+
print_endline "Completion Frontend did not return completable");
37+
None
3438
| Some (completable, scope) -> (
39+
let _ =
40+
match source with
41+
| Some text -> (
42+
match SharedTypes.CompletableRevamped.try_loc completable with
43+
| Some loc ->
44+
let range =
45+
CodeFence.
46+
{
47+
start = loc.Location.loc_start.pos_cnum;
48+
finish = loc.Warnings.loc_end.pos_cnum;
49+
}
50+
in
51+
Printf.printf "Found Completable: %s\n\n"
52+
(SharedTypes.CompletableRevamped.toString completable);
53+
CodeFence.format_code_snippet_cropped text (Some range) 3
54+
|> print_endline
55+
| None -> ())
56+
| None -> ()
57+
in
58+
3559
(* Only perform expensive ast operations if there are completables *)
3660
match Cmt.loadFullCmtFromPath ~path with
3761
| 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)