Skip to content

Commit e3d1470

Browse files
committed
allow extra module completions also for pipe
1 parent af54200 commit e3d1470

File tree

4 files changed

+153
-104
lines changed

4 files changed

+153
-104
lines changed

analysis/src/CompletionBackEnd.ml

Lines changed: 44 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,30 @@ let getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope
635635
findAllCompletions ~env ~prefix ~exact ~namesUsed ~completionContext
636636
| None -> [])
637637

638+
(** Completions intended for piping, from a completion path. *)
639+
let completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug ~prefix ~env
640+
~rawOpens ~full completionPath =
641+
let completionPathMinusOpens =
642+
TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package:full.package
643+
completionPath
644+
|> String.concat "."
645+
in
646+
let completionName name =
647+
if completionPathMinusOpens = "" then name
648+
else completionPathMinusOpens ^ "." ^ name
649+
in
650+
let completions =
651+
completionPath @ [prefix]
652+
|> getCompletionsForPath ~debug ~completionContext:Value ~exact:false ~opens
653+
~full ~pos ~env ~scope
654+
in
655+
let completions =
656+
completions
657+
|> List.map (fun (completion : Completion.t) ->
658+
{completion with name = completionName completion.name; env})
659+
in
660+
completions
661+
638662
let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env
639663
~scope path =
640664
match
@@ -1028,62 +1052,10 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10281052
let pipeCompletionsForModule =
10291053
match pipeCompletion with
10301054
| Some (path, completionPath) ->
1031-
(* Most of this is copied from the pipe completion code. Should probably be unified. *)
1032-
let completions =
1033-
completionPath @ [fieldName]
1034-
|> getCompletionsForPath ~debug ~completionContext:Value
1035-
~exact:false ~opens ~full ~pos ~env ~scope
1036-
in
1037-
let completionPathMinusOpens =
1038-
TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package
1039-
completionPath
1040-
|> String.concat "."
1041-
in
1042-
let completionName name =
1043-
if completionPathMinusOpens = "" then name
1044-
else completionPathMinusOpens ^ "." ^ name
1045-
in
1046-
(* Find all functions in the module that takes type t *)
1047-
let rec fnTakesTypeT t =
1048-
match t.Types.desc with
1049-
| Tlink t1
1050-
| Tsubst t1
1051-
| Tpoly (t1, [])
1052-
| Tconstr (Pident {name = "function$"}, [t1; _], _) ->
1053-
fnTakesTypeT t1
1054-
| Tarrow _ -> (
1055-
match
1056-
TypeUtils.extractFunctionType ~env ~package:full.package t
1057-
with
1058-
| (Nolabel, {desc = Tconstr (p, _, _)}) :: _, _ ->
1059-
Path.same p path || Path.name p = "t"
1060-
| _ -> false)
1061-
| _ -> false
1062-
in
1063-
completions
1064-
|> List.filter_map (fun (completion : Completion.t) ->
1065-
match completion.kind with
1066-
| Value t when fnTakesTypeT t ->
1067-
let name = completionName completion.name in
1068-
let nameWithPipe = "->" ^ name in
1069-
(* TODO: We need to add support for setting the text insertion location explicitly,
1070-
so we can account for the dot that triggered the completion, but that we want
1071-
removed when inserting the pipe. This means we also need to track the loc of
1072-
the dot + the identifier that we're filtering with (fieldName here).
1073-
That should be easy to do by extending CPField. *)
1074-
Some
1075-
{
1076-
completion with
1077-
name = nameWithPipe;
1078-
sortText =
1079-
Some
1080-
(name |> String.split_on_char '.' |> List.rev
1081-
|> List.hd);
1082-
insertText = Some nameWithPipe;
1083-
env;
1084-
range = Some fieldNameLoc;
1085-
}
1086-
| _ -> None)
1055+
completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug
1056+
~prefix:fieldName ~env ~rawOpens ~full completionPath
1057+
|> TypeUtils.filterPipeableFunctions ~env ~full ~path
1058+
~replaceRange:fieldNameLoc
10871059
| None -> []
10881060
in
10891061
pipeCompletionsForModule
@@ -1134,6 +1106,10 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
11341106
with
11351107
| None -> []
11361108
| Some (typ, envFromCompletionItem) -> (
1109+
(* Extract any module to draw extra completions from for the identified type. *)
1110+
let extraModuleToCompleteFrom =
1111+
TypeUtils.getExtraModuleToCompleteFromForType typ ~env ~full
1112+
in
11371113
let env, typ =
11381114
typ
11391115
|> TypeUtils.resolveTypeForPipeCompletion ~env ~package ~full ~lhsLoc
@@ -1184,32 +1160,20 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
11841160
~envFromItem:envFromCompletionItem (Utils.expandPath path)
11851161
| _ -> None)
11861162
in
1163+
let completionsFromExtraModule =
1164+
match extraModuleToCompleteFrom with
1165+
| None -> []
1166+
| Some completionPath ->
1167+
completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug
1168+
~prefix:funNamePrefix ~env ~rawOpens ~full completionPath
1169+
in
11871170
match completionPath with
11881171
| Some completionPath -> (
1189-
let completionPathMinusOpens =
1190-
TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package
1191-
completionPath
1192-
|> String.concat "."
1193-
in
1194-
let completionName name =
1195-
if completionPathMinusOpens = "" then name
1196-
else completionPathMinusOpens ^ "." ^ name
1197-
in
1198-
let completions =
1199-
completionPath @ [funNamePrefix]
1200-
|> getCompletionsForPath ~debug ~completionContext:Value ~exact:false
1201-
~opens ~full ~pos ~env ~scope
1202-
in
1203-
let completions =
1204-
completions
1205-
|> List.map (fun (completion : Completion.t) ->
1206-
{
1207-
completion with
1208-
name = completionName completion.name;
1209-
env
1210-
(* Restore original env for the completion after x->foo()... *);
1211-
})
1172+
let completionsFromMainFn =
1173+
completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug
1174+
~prefix:funNamePrefix ~env ~rawOpens ~full completionPath
12121175
in
1176+
let completions = completionsFromMainFn @ completionsFromExtraModule in
12131177
(* We add React element functions to the completion if we're in a JSX context *)
12141178
let forJsxCompletion =
12151179
if inJsx then
@@ -1246,7 +1210,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
12461210
]
12471211
@ completions
12481212
| _ -> completions)
1249-
| None -> []))
1213+
| None -> completionsFromExtraModule))
12501214
| CTuple ctxPaths ->
12511215
if Debug.verbose () then print_endline "[ctx_path]--> CTuple";
12521216
(* Turn a list of context paths into a list of type expressions. *)

analysis/src/TypeUtils.ml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,3 +1140,59 @@ let pathToElementProps package =
11401140
match package.genericJsxModule with
11411141
| None -> ["ReactDOM"; "domProps"]
11421142
| Some g -> (g |> String.split_on_char '.') @ ["Elements"; "props"]
1143+
1144+
(** Extracts module to draw extra completions from for the type, if it has been annotated with @editor.completeFrom. *)
1145+
let getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) =
1146+
match t |> Shared.digConstructor with
1147+
| Some path -> (
1148+
match References.digConstructor ~env ~package:full.package path with
1149+
| None -> None
1150+
(*| Some (env, {item = {decl = {type_manifest = Some t}}}) ->
1151+
getExtraModuleToCompleteFromForType ~env ~full t
1152+
1153+
This could be commented back in to traverse type aliases.
1154+
Not clear as of now if that makes sense to do or not.
1155+
*)
1156+
| Some (_, {item = {attributes}}) ->
1157+
ProcessAttributes.findEditorCompleteFromAttribute attributes)
1158+
| None -> None
1159+
1160+
(** Checks whether the provided type represents a function that takes the provided path
1161+
as the first argument (meaning it's pipeable). *)
1162+
let rec fnTakesType ~env ~full ~path t =
1163+
match t.Types.desc with
1164+
| Tlink t1
1165+
| Tsubst t1
1166+
| Tpoly (t1, [])
1167+
| Tconstr (Pident {name = "function$"}, [t1; _], _) ->
1168+
fnTakesType ~env ~full ~path t1
1169+
| Tarrow _ -> (
1170+
match extractFunctionType ~env ~package:full.package t with
1171+
| (Nolabel, {desc = Tconstr (p, _, _)}) :: _, _ ->
1172+
Path.same p path || Path.name p = "t"
1173+
| _ -> false)
1174+
| _ -> false
1175+
1176+
(** Turns a completion into a pipe completion. *)
1177+
let transformCompletionToPipeCompletion ~env ~replaceRange
1178+
(completion : Completion.t) =
1179+
let name = completion.name in
1180+
let nameWithPipe = "->" ^ name in
1181+
Some
1182+
{
1183+
completion with
1184+
name = nameWithPipe;
1185+
sortText = Some (name |> String.split_on_char '.' |> List.rev |> List.hd);
1186+
insertText = Some nameWithPipe;
1187+
env;
1188+
range = Some replaceRange;
1189+
}
1190+
1191+
(** Filters out completions that are not pipeable from a list of completions. *)
1192+
let filterPipeableFunctions ~env ~full ~path ~replaceRange completions =
1193+
completions
1194+
|> List.filter_map (fun (completion : Completion.t) ->
1195+
match completion.kind with
1196+
| Value t when fnTakesType ~env ~full ~path t ->
1197+
transformCompletionToPipeCompletion ~env ~replaceRange completion
1198+
| _ -> None)

analysis/tests/src/CompletionFromModule.res

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ module SomeModule = {
88

99
let n = {SomeModule.name: "hello"}
1010

11-
// ^dv+
1211
// n.
1312
// ^com
14-
// ^dv-
1513

1614
@editor.completeFrom(SomeOtherModule)
1715
type typeOutsideModule = {nname: string}
@@ -30,10 +28,13 @@ module SomeOtherModule = {
3028

3129
let nn: SomeOtherModule.t = {nname: "hello"}
3230

33-
// ^dv+
3431
// nn.
3532
// ^com
36-
// ^dv-
3733

3834
// @editor.completeFrom(SomeOthe) type typeOutsideModule = {nname: string}
3935
// ^com
36+
37+
let nnn: typeOutsideModule = {nname: "hello"}
38+
39+
// nnn->
40+
// ^com

analysis/tests/src/expected/CompletionFromModule.res.txt

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
2-
Complete src/CompletionFromModule.res 11:5
3-
posCursor:[11:5] posNoWhite:[11:4] Found expr:[11:3->11:5]
4-
Pexp_field [11:3->11:4] _:[15:0->11:5]
5-
[set_result] set new result to Cpath Value[n].""
1+
Complete src/CompletionFromModule.res 10:5
2+
posCursor:[10:5] posNoWhite:[10:4] Found expr:[10:3->10:5]
3+
Pexp_field [10:3->10:4] _:[13:0->10:5]
64
Completable: Cpath Value[n].""
75
Package opens Pervasives.JsxModules.place holder
86
Resolved opens 1 pervasives
97
ContextPath Value[n].""
10-
[ctx_path]--> CPField
118
ContextPath Value[n]
12-
[ctx_path]--> CPId
139
Path n
1410
Path SomeModule.
1511
[{
@@ -20,7 +16,7 @@ Path SomeModule.
2016
"documentation": null,
2117
"sortText": "getName",
2218
"textEdit": {
23-
"range": {"start": {"line": 11, "character": 4}, "end": {"line": 11, "character": 4}},
19+
"range": {"start": {"line": 10, "character": 4}, "end": {"line": 10, "character": 4}},
2420
"newText": "->SomeModule.getName"
2521
}
2622
}, {
@@ -31,19 +27,14 @@ Path SomeModule.
3127
"documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"}
3228
}]
3329

34-
35-
36-
Complete src/CompletionFromModule.res 33:6
37-
posCursor:[33:6] posNoWhite:[33:5] Found expr:[33:3->33:6]
38-
Pexp_field [33:3->33:5] _:[40:0->33:6]
39-
[set_result] set new result to Cpath Value[nn].""
30+
Complete src/CompletionFromModule.res 30:6
31+
posCursor:[30:6] posNoWhite:[30:5] Found expr:[30:3->30:6]
32+
Pexp_field [30:3->30:5] _:[36:0->30:6]
4033
Completable: Cpath Value[nn].""
4134
Package opens Pervasives.JsxModules.place holder
4235
Resolved opens 1 pervasives
4336
ContextPath Value[nn].""
44-
[ctx_path]--> CPField
4537
ContextPath Value[nn]
46-
[ctx_path]--> CPId
4738
Path nn
4839
Path SomeOtherModule.
4940
[{
@@ -54,7 +45,7 @@ Path SomeOtherModule.
5445
"documentation": null,
5546
"sortText": "getNName",
5647
"textEdit": {
57-
"range": {"start": {"line": 33, "character": 5}, "end": {"line": 33, "character": 5}},
48+
"range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}},
5849
"newText": "->SomeOtherModule.getNName"
5950
}
6051
}, {
@@ -65,7 +56,7 @@ Path SomeOtherModule.
6556
"documentation": null,
6657
"sortText": "getNName2",
6758
"textEdit": {
68-
"range": {"start": {"line": 33, "character": 5}, "end": {"line": 33, "character": 5}},
59+
"range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}},
6960
"newText": "->SomeOtherModule.getNName2"
7061
}
7162
}, {
@@ -76,8 +67,7 @@ Path SomeOtherModule.
7667
"documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"}
7768
}]
7869

79-
80-
Complete src/CompletionFromModule.res 37:32
70+
Complete src/CompletionFromModule.res 33:32
8171
XXX Not found!
8272
Completable: Cpath Module[SomeOthe]
8373
Package opens Pervasives.JsxModules.place holder
@@ -92,3 +82,41 @@ Path SomeOthe
9282
"documentation": null
9383
}]
9484

85+
Complete src/CompletionFromModule.res 38:8
86+
posCursor:[38:8] posNoWhite:[38:7] Found expr:[38:3->0:-1]
87+
Completable: Cpath Value[nnn]->
88+
Package opens Pervasives.JsxModules.place holder
89+
Resolved opens 1 pervasives
90+
ContextPath Value[nnn]->
91+
ContextPath Value[nnn]
92+
Path nnn
93+
CPPipe env:CompletionFromModule
94+
CPPipe type path:typeOutsideModule
95+
CPPipe pathFromEnv: found:true
96+
Path SomeOtherModule.
97+
[{
98+
"label": "SomeOtherModule.getNName",
99+
"kind": 12,
100+
"tags": [],
101+
"detail": "t => string",
102+
"documentation": null
103+
}, {
104+
"label": "SomeOtherModule.getNName3",
105+
"kind": 12,
106+
"tags": [],
107+
"detail": "irrelevantType => string",
108+
"documentation": null
109+
}, {
110+
"label": "SomeOtherModule.thisShouldNotBeCompletedFor",
111+
"kind": 12,
112+
"tags": [],
113+
"detail": "unit => string",
114+
"documentation": null
115+
}, {
116+
"label": "SomeOtherModule.getNName2",
117+
"kind": 12,
118+
"tags": [],
119+
"detail": "typeOutsideModule => string",
120+
"documentation": null
121+
}]
122+

0 commit comments

Comments
 (0)