Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 90d3b3b

Browse files
committedNov 20, 2024·
start refactoring dot completion everywhere
1 parent a881b26 commit 90d3b3b

File tree

4 files changed

+439
-21
lines changed

4 files changed

+439
-21
lines changed
 

‎analysis/src/CompletionBackEnd.ml

+112-14
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,105 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
993993
path @ [fieldName]
994994
|> getCompletionsForPath ~debug ~opens ~full ~pos ~exact
995995
~completionContext:Field ~env ~scope
996+
| CPField {contextPath = cp; fieldName; fieldNameLoc} when Debug.verbose () ->
997+
(* TODO: this should only happen when the dot completion is at the end of the path *)
998+
if Debug.verbose () then print_endline "[ctx_path]--> dot completion!";
999+
let completionsForCtxPath =
1000+
cp
1001+
|> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
1002+
~exact:true ~scope
1003+
|> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
1004+
in
1005+
(* These are the main completions for the dot. *)
1006+
let mainCompletions =
1007+
match completionsForCtxPath with
1008+
| Some (typ, env)
1009+
when typ |> TypeUtils.extractObjectType ~env ~package |> Option.is_some
1010+
->
1011+
(* Handle obj completion via dot *)
1012+
if Debug.verbose () then
1013+
Printf.printf "[dot_completion]--> Obj type found:\n";
1014+
let objEnv, obj =
1015+
typ |> TypeUtils.extractObjectType ~env ~package |> Option.get
1016+
in
1017+
obj |> TypeUtils.getObjFields
1018+
|> Utils.filterMap (fun (field, typ) ->
1019+
if Utils.checkName field ~prefix:fieldName ~exact then
1020+
let fullObjFieldName = Printf.sprintf "[\"%s\"]" field in
1021+
Some
1022+
(Completion.create fullObjFieldName ~range:fieldNameLoc
1023+
~insertText:fullObjFieldName ~env:objEnv
1024+
~kind:(Completion.ObjLabel typ))
1025+
else None)
1026+
| Some (typ, env)
1027+
when typ |> TypeUtils.extractRecordType ~env ~package |> Option.is_some
1028+
->
1029+
let env, fields, decl, _path, _attributes =
1030+
typ |> TypeUtils.extractRecordType ~env ~package |> Option.get
1031+
in
1032+
if Debug.verbose () then
1033+
Printf.printf "[dot_completion]--> Record type found\n";
1034+
let recordAsString =
1035+
decl.item.decl |> Shared.declToString decl.name.txt
1036+
in
1037+
fields
1038+
|> Utils.filterMap (fun field ->
1039+
if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then
1040+
Some
1041+
(Completion.create field.fname.txt ~env
1042+
?deprecated:field.deprecated ~docstring:field.docstring
1043+
~kind:(Completion.Field (field, recordAsString)))
1044+
else None)
1045+
| Some (_typ, _env) ->
1046+
(* No more primary completions, for now. *)
1047+
[]
1048+
| None -> []
1049+
in
1050+
let pipeCompletions =
1051+
match completionsForCtxPath with
1052+
| None -> []
1053+
| Some (typ, envFromCompletionItem) -> (
1054+
let tPath = TypeUtils.pathFromTypeExpr typ in
1055+
match tPath with
1056+
| None -> []
1057+
| Some tPath ->
1058+
let completionPath =
1059+
(tPath |> Utils.expandPath |> List.tl |> List.rev)
1060+
@ (envFromCompletionItem.pathRev |> List.rev)
1061+
in
1062+
if List.length completionPath = 0 then []
1063+
else
1064+
let completions =
1065+
completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom
1066+
~opens ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full
1067+
completionPath
1068+
in
1069+
completions
1070+
|> TypeUtils.filterPipeableFunctions ~env ~full
1071+
~lastPath:(Path.last tPath) ~replaceRange:fieldNameLoc)
1072+
in
1073+
(* Extra completions from configure extra module(s) *)
1074+
let extraCompletions =
1075+
match completionsForCtxPath with
1076+
| None -> []
1077+
| Some (typ, envFromCompletionItem) -> (
1078+
match
1079+
TypeUtils.getExtraModuleToCompleteFromForType typ
1080+
~env:envFromCompletionItem ~full
1081+
with
1082+
| None -> []
1083+
| Some completionPath ->
1084+
completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens
1085+
~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full
1086+
completionPath
1087+
|> TypeUtils.filterPipeableFunctions ~env ~full
1088+
~replaceRange:fieldNameLoc
1089+
?lastPath:
1090+
(match TypeUtils.pathFromTypeExpr typ with
1091+
| None -> None
1092+
| Some tPath -> Some (Path.last tPath)))
1093+
in
1094+
mainCompletions @ pipeCompletions @ extraCompletions
9961095
| CPField {contextPath = cp; fieldName; fieldNameLoc} -> (
9971096
if Debug.verbose () then print_endline "[ctx_path]--> CPField";
9981097
let completionsForCtxPath =
@@ -1056,8 +1155,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10561155
completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug
10571156
~prefix:fieldName ~envCompletionIsMadeFrom:env ~env:envFromExtracted
10581157
~rawOpens ~full completionPath
1059-
|> TypeUtils.filterPipeableFunctions ~env:envFromExtracted ~full ~path
1060-
~replaceRange:fieldNameLoc
1158+
|> TypeUtils.filterPipeableFunctions ~env:envFromExtracted ~full
1159+
~lastPath:(Path.last path) ~replaceRange:fieldNameLoc
10611160
| None -> []
10621161
in
10631162
pipeCompletionsForModule
@@ -1081,16 +1180,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10811180
| Some (typ, env) -> (
10821181
match typ |> TypeUtils.extractObjectType ~env ~package with
10831182
| Some (env, tObj) ->
1084-
let rec getFields (texp : Types.type_expr) =
1085-
match texp.desc with
1086-
| Tfield (name, _, t1, t2) ->
1087-
let fields = t2 |> getFields in
1088-
(name, t1) :: fields
1089-
| Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFields
1090-
| Tvar None -> []
1091-
| _ -> []
1092-
in
1093-
tObj |> getFields
1183+
tObj |> TypeUtils.getObjFields
10941184
|> Utils.filterMap (fun (field, typ) ->
10951185
if Utils.checkName field ~prefix:label ~exact then
10961186
Some
@@ -1175,15 +1265,23 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
11751265
completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens
11761266
~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full
11771267
completionPath
1178-
|> TypeUtils.filterPipeableFunctions ~env ~full ?path:tPath
1268+
|> TypeUtils.filterPipeableFunctions ~env ~full
1269+
?lastPath:
1270+
(match tPath with
1271+
| None -> None
1272+
| Some tPath -> Some (Path.last tPath))
11791273
in
11801274
match completionPath with
11811275
| Some completionPath -> (
11821276
let completionsFromMainFn =
11831277
completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens
11841278
~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full
11851279
completionPath
1186-
|> TypeUtils.filterPipeableFunctions ~env ~full ?path:tPath
1280+
|> TypeUtils.filterPipeableFunctions ~env ~full
1281+
?lastPath:
1282+
(match tPath with
1283+
| None -> None
1284+
| Some tPath -> Some (Path.last tPath))
11871285
in
11881286
let completions = completionsFromMainFn @ completionsFromExtraModule in
11891287
(* We add React element functions to the completion if we're in a JSX context *)

‎analysis/src/TypeUtils.ml

+16-7
Original file line numberDiff line numberDiff line change
@@ -1159,13 +1159,13 @@ let getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) =
11591159

11601160
(** Checks whether the provided type represents a function that takes the provided path
11611161
as the first argument (meaning it's pipeable). *)
1162-
let rec fnTakesTypeAsFirstArg ~env ~full ~path t =
1162+
let rec fnTakesTypeAsFirstArg ~env ~full ~lastPath t =
11631163
match t.Types.desc with
11641164
| Tlink t1
11651165
| Tsubst t1
11661166
| Tpoly (t1, [])
11671167
| Tconstr (Pident {name = "function$"}, [t1; _], _) ->
1168-
fnTakesTypeAsFirstArg ~env ~full ~path t1
1168+
fnTakesTypeAsFirstArg ~env ~full ~lastPath t1
11691169
| Tarrow _ -> (
11701170
match extractFunctionType ~env ~package:full.package t with
11711171
| (Nolabel, t) :: _, _ -> (
@@ -1181,7 +1181,7 @@ let rec fnTakesTypeAsFirstArg ~env ~full ~path t =
11811181
Therefore, we can safely pluck out just the last part of the `path`, but need to use the entire name of the current type
11821182
we're comparing with.
11831183
*)
1184-
Path.name p = Path.last path || Path.name p = "t")
1184+
Path.name p = lastPath || Path.name p = "t")
11851185
| _ -> false)
11861186
| _ -> false
11871187

@@ -1201,14 +1201,14 @@ let transformCompletionToPipeCompletion ~env ~replaceRange
12011201
}
12021202

12031203
(** Filters out completions that are not pipeable from a list of completions. *)
1204-
let filterPipeableFunctions ~env ~full ?path ?replaceRange completions =
1205-
match path with
1204+
let filterPipeableFunctions ~env ~full ?lastPath ?replaceRange completions =
1205+
match lastPath with
12061206
| None -> completions
1207-
| Some path ->
1207+
| Some lastPath ->
12081208
completions
12091209
|> List.filter_map (fun (completion : Completion.t) ->
12101210
match completion.kind with
1211-
| Value t when fnTakesTypeAsFirstArg ~env ~full ~path t -> (
1211+
| Value t when fnTakesTypeAsFirstArg ~env ~full ~lastPath t -> (
12121212
match replaceRange with
12131213
| None -> Some completion
12141214
| Some replaceRange ->
@@ -1222,3 +1222,12 @@ let removeCurrentModuleIfNeeded ~envCompletionIsMadeFrom completionPath =
12221222
&& List.hd completionPath = envCompletionIsMadeFrom.QueryEnv.file.moduleName
12231223
then List.tl completionPath
12241224
else completionPath
1225+
1226+
let rec getObjFields (texp : Types.type_expr) =
1227+
match texp.desc with
1228+
| Tfield (name, _, t1, t2) ->
1229+
let fields = t2 |> getObjFields in
1230+
(name, t1) :: fields
1231+
| Tlink te | Tsubst te | Tpoly (te, []) -> te |> getObjFields
1232+
| Tvar None -> []
1233+
| _ -> []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
let someObj = {
2+
"name": "hello",
3+
"age": 123,
4+
}
5+
// ^dv+
6+
// someObj.
7+
// ^com
8+
9+
// someObj.na
10+
// ^com
11+
12+
type rrr = {name: string}
13+
let rrr = {name: "hello"}
14+
15+
// rrr.n
16+
// ^com
17+
18+
module SomeMod = {
19+
module SomeOtherMod = {
20+
type x
21+
22+
@send external do: x => unit = "do"
23+
24+
external xx: x = "xx"
25+
}
26+
}
27+
28+
external x: SomeMod.SomeOtherMod.x = "x"
29+
30+
// x.
31+
// ^com
32+
33+
// SomeMod.SomeOtherMod.xx.
34+
// ^com
35+
36+
module Sss = {
37+
type rrr = {name: string}
38+
let rrr = {name: "hello"}
39+
let do = rrr => rrr.name
40+
}
41+
42+
// Sss.rrr.
43+
// ^com
44+
45+
@editor.completeFrom(DotCompletionEverywhere.X2)
46+
type x2x2 = {namee: string}
47+
let x2x2 = {namee: "hello"}
48+
49+
module X2 = {
50+
let stuff = x => x.namee
51+
}
52+
53+
// x2x2.
54+
// ^com
55+
56+
let obj = {
57+
"name": "ReScript",
58+
"number": 1,
59+
"nothing": true,
60+
}
61+
62+
// obj.
63+
// ^com
64+
65+
// ^dv-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
2+
Complete src/DotCompletionEverywhere.res 5:11
3+
posCursor:[5:11] posNoWhite:[5:10] Found expr:[5:3->5:11]
4+
Pexp_field [5:3->5:10] _:[11:0->5:11]
5+
[set_result] set new result to Cpath Value[someObj].""
6+
Completable: Cpath Value[someObj].""
7+
Package opens Pervasives.JsxModules.place holder
8+
Resolved opens 1 pervasives
9+
ContextPath Value[someObj].""
10+
[ctx_path]--> dot completion!
11+
ContextPath Value[someObj]
12+
[ctx_path]--> CPId
13+
Path someObj
14+
[dot_completion]--> Obj type found:
15+
[{
16+
"label": "[\"age\"]",
17+
"kind": 4,
18+
"tags": [],
19+
"detail": "int",
20+
"documentation": null,
21+
"textEdit": {
22+
"range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 10}},
23+
"newText": "[\"age\"]"
24+
}
25+
}, {
26+
"label": "[\"name\"]",
27+
"kind": 4,
28+
"tags": [],
29+
"detail": "string",
30+
"documentation": null,
31+
"textEdit": {
32+
"range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 10}},
33+
"newText": "[\"name\"]"
34+
}
35+
}]
36+
37+
Complete src/DotCompletionEverywhere.res 8:13
38+
posCursor:[8:13] posNoWhite:[8:12] Found expr:[8:3->8:13]
39+
Pexp_field [8:3->8:10] na:[8:11->8:13]
40+
[set_result] set new result to Cpath Value[someObj].na
41+
Completable: Cpath Value[someObj].na
42+
Package opens Pervasives.JsxModules.place holder
43+
Resolved opens 1 pervasives
44+
ContextPath Value[someObj].na
45+
[ctx_path]--> dot completion!
46+
ContextPath Value[someObj]
47+
[ctx_path]--> CPId
48+
Path someObj
49+
[dot_completion]--> Obj type found:
50+
[{
51+
"label": "[\"name\"]",
52+
"kind": 4,
53+
"tags": [],
54+
"detail": "string",
55+
"documentation": null,
56+
"textEdit": {
57+
"range": {"start": {"line": 8, "character": 11}, "end": {"line": 8, "character": 13}},
58+
"newText": "[\"name\"]"
59+
}
60+
}]
61+
62+
Complete src/DotCompletionEverywhere.res 14:8
63+
posCursor:[14:8] posNoWhite:[14:7] Found expr:[14:3->14:8]
64+
Pexp_field [14:3->14:6] n:[14:7->14:8]
65+
[set_result] set new result to Cpath Value[rrr].n
66+
Completable: Cpath Value[rrr].n
67+
Package opens Pervasives.JsxModules.place holder
68+
Resolved opens 1 pervasives
69+
ContextPath Value[rrr].n
70+
[ctx_path]--> dot completion!
71+
ContextPath Value[rrr]
72+
[ctx_path]--> CPId
73+
Path rrr
74+
[dot_completion]--> Record type found
75+
[{
76+
"label": "name",
77+
"kind": 5,
78+
"tags": [],
79+
"detail": "string",
80+
"documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"}
81+
}]
82+
83+
Complete src/DotCompletionEverywhere.res 29:5
84+
posCursor:[29:5] posNoWhite:[29:4] Found expr:[29:3->29:5]
85+
Pexp_field [29:3->29:4] _:[35:0->29:5]
86+
[set_result] set new result to Cpath Value[x].""
87+
Completable: Cpath Value[x].""
88+
Package opens Pervasives.JsxModules.place holder
89+
Resolved opens 1 pervasives
90+
ContextPath Value[x].""
91+
[ctx_path]--> dot completion!
92+
ContextPath Value[x]
93+
[ctx_path]--> CPId
94+
Path x
95+
Path SomeMod.SomeOtherMod.
96+
[{
97+
"label": "->SomeMod.SomeOtherMod.do",
98+
"kind": 12,
99+
"tags": [],
100+
"detail": "x => unit",
101+
"documentation": null,
102+
"sortText": "do",
103+
"textEdit": {
104+
"range": {"start": {"line": 29, "character": 4}, "end": {"line": 29, "character": 4}},
105+
"newText": "->SomeMod.SomeOtherMod.do"
106+
}
107+
}]
108+
109+
Complete src/DotCompletionEverywhere.res 32:27
110+
posCursor:[32:27] posNoWhite:[32:26] Found expr:[32:3->32:27]
111+
Pexp_field [32:3->32:26] _:[35:0->32:27]
112+
[set_result] set new result to Cpath Value[SomeMod, SomeOtherMod, xx].""
113+
Completable: Cpath Value[SomeMod, SomeOtherMod, xx].""
114+
Package opens Pervasives.JsxModules.place holder
115+
Resolved opens 1 pervasives
116+
ContextPath Value[SomeMod, SomeOtherMod, xx].""
117+
[ctx_path]--> dot completion!
118+
ContextPath Value[SomeMod, SomeOtherMod, xx]
119+
[ctx_path]--> CPId
120+
Path SomeMod.SomeOtherMod.xx
121+
Path SomeMod.SomeOtherMod.
122+
[{
123+
"label": "->SomeMod.SomeOtherMod.do",
124+
"kind": 12,
125+
"tags": [],
126+
"detail": "x => unit",
127+
"documentation": null,
128+
"sortText": "do",
129+
"textEdit": {
130+
"range": {"start": {"line": 32, "character": 26}, "end": {"line": 32, "character": 26}},
131+
"newText": "->SomeMod.SomeOtherMod.do"
132+
}
133+
}]
134+
135+
Complete src/DotCompletionEverywhere.res 41:11
136+
posCursor:[41:11] posNoWhite:[41:10] Found expr:[41:3->41:11]
137+
Pexp_field [41:3->41:10] _:[44:0->41:11]
138+
[set_result] set new result to Cpath Value[Sss, rrr].""
139+
Completable: Cpath Value[Sss, rrr].""
140+
Package opens Pervasives.JsxModules.place holder
141+
Resolved opens 1 pervasives
142+
ContextPath Value[Sss, rrr].""
143+
[ctx_path]--> dot completion!
144+
ContextPath Value[Sss, rrr]
145+
[ctx_path]--> CPId
146+
Path Sss.rrr
147+
[dot_completion]--> Record type found
148+
Path Sss.
149+
[{
150+
"label": "name",
151+
"kind": 5,
152+
"tags": [],
153+
"detail": "string",
154+
"documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"}
155+
}, {
156+
"label": "->Sss.do",
157+
"kind": 12,
158+
"tags": [],
159+
"detail": "rrr => string",
160+
"documentation": null,
161+
"sortText": "do",
162+
"textEdit": {
163+
"range": {"start": {"line": 41, "character": 10}, "end": {"line": 41, "character": 10}},
164+
"newText": "->Sss.do"
165+
}
166+
}]
167+
168+
Complete src/DotCompletionEverywhere.res 52:8
169+
posCursor:[52:8] posNoWhite:[52:7] Found expr:[52:3->52:8]
170+
Pexp_field [52:3->52:7] _:[55:0->52:8]
171+
[set_result] set new result to Cpath Value[x2x2].""
172+
Completable: Cpath Value[x2x2].""
173+
Package opens Pervasives.JsxModules.place holder
174+
Resolved opens 1 pervasives
175+
ContextPath Value[x2x2].""
176+
[ctx_path]--> dot completion!
177+
ContextPath Value[x2x2]
178+
[ctx_path]--> CPId
179+
Path x2x2
180+
[dot_completion]--> Record type found
181+
Path X2.
182+
[{
183+
"label": "namee",
184+
"kind": 5,
185+
"tags": [],
186+
"detail": "string",
187+
"documentation": {"kind": "markdown", "value": "```rescript\nnamee: string\n```\n\n```rescript\ntype x2x2 = {namee: string}\n```"}
188+
}, {
189+
"label": "->X2.stuff",
190+
"kind": 12,
191+
"tags": [],
192+
"detail": "x2x2 => string",
193+
"documentation": null,
194+
"sortText": "stuff",
195+
"textEdit": {
196+
"range": {"start": {"line": 52, "character": 7}, "end": {"line": 52, "character": 7}},
197+
"newText": "->X2.stuff"
198+
}
199+
}]
200+
201+
Complete src/DotCompletionEverywhere.res 61:7
202+
posCursor:[61:7] posNoWhite:[61:6] Found expr:[61:3->61:7]
203+
Pexp_field [61:3->61:6] _:[66:0->61:7]
204+
[set_result] set new result to Cpath Value[obj].""
205+
Completable: Cpath Value[obj].""
206+
Package opens Pervasives.JsxModules.place holder
207+
Resolved opens 1 pervasives
208+
ContextPath Value[obj].""
209+
[ctx_path]--> dot completion!
210+
ContextPath Value[obj]
211+
[ctx_path]--> CPId
212+
Path obj
213+
[dot_completion]--> Obj type found:
214+
[{
215+
"label": "[\"name\"]",
216+
"kind": 4,
217+
"tags": [],
218+
"detail": "string",
219+
"documentation": null,
220+
"textEdit": {
221+
"range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}},
222+
"newText": "[\"name\"]"
223+
}
224+
}, {
225+
"label": "[\"nothing\"]",
226+
"kind": 4,
227+
"tags": [],
228+
"detail": "bool",
229+
"documentation": null,
230+
"textEdit": {
231+
"range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}},
232+
"newText": "[\"nothing\"]"
233+
}
234+
}, {
235+
"label": "[\"number\"]",
236+
"kind": 4,
237+
"tags": [],
238+
"detail": "int",
239+
"documentation": null,
240+
"textEdit": {
241+
"range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}},
242+
"newText": "[\"number\"]"
243+
}
244+
}]
245+
246+

0 commit comments

Comments
 (0)
Please sign in to comment.