Skip to content

Commit 7e69428

Browse files
committed
functions and labels
1 parent 4200241 commit 7e69428

File tree

4 files changed

+154
-38
lines changed

4 files changed

+154
-38
lines changed

analysis/src/CompletionFrontEndNew.ml

+127-36
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@ module CompletionInstruction = struct
238238
to the record itself. *)
239239
prefix: string;
240240
} (** Completing inside of an expression. *)
241+
| CnamedArg of {
242+
ctxPath: ctxPath;
243+
(** Context path to the function with the argument. *)
244+
seenLabels: string list;
245+
(** All the already seen labels in the function call. *)
246+
prefix: string; (** The text the user has written so far.*)
247+
}
241248

242249
let ctxPath ctxPath = CtxPath ctxPath
243250

@@ -257,6 +264,9 @@ module CompletionInstruction = struct
257264
ctxPath = completionContext.ctxPath;
258265
}
259266

267+
let namedArg ~prefix ~functionContextPath ~seenLabels =
268+
CnamedArg {prefix; ctxPath = functionContextPath; seenLabels}
269+
260270
let toString (c : t) =
261271
match c with
262272
| CtxPath ctxPath ->
@@ -276,6 +286,10 @@ module CompletionInstruction = struct
276286
(match prefix with
277287
| "" -> ""
278288
| prefix -> Printf.sprintf ", prefix: \"%s\"" prefix)
289+
| CnamedArg {prefix; ctxPath; seenLabels} ->
290+
"CnamedArg("
291+
^ (ctxPath |> ctxPathToString)
292+
^ ", " ^ str prefix ^ ", " ^ (seenLabels |> list) ^ ")"
279293
end
280294

281295
module CompletionResult = struct
@@ -298,6 +312,12 @@ module CompletionResult = struct
298312
Some
299313
( CompletionInstruction.expression ~completionContext ~prefix,
300314
completionContext )
315+
316+
let namedArg ~(completionContext : CompletionContext.t) ~prefix ~seenLabels
317+
~functionContextPath =
318+
Some
319+
( CompletionInstruction.namedArg ~functionContextPath ~prefix ~seenLabels,
320+
completionContext )
301321
end
302322

303323
let flattenLidCheckDot ?(jsx = true) ~(completionContext : CompletionContext.t)
@@ -908,55 +928,126 @@ and completeExpr ~completionContext (expr : Parsetree.expression) :
908928
if locHasPos lhs.pexp_loc then completeExpr ~completionContext lhs
909929
else if locHasPos rhs.pexp_loc then completeExpr ~completionContext rhs
910930
else None
931+
| Pexp_apply (fnExpr, _args) when locHasPos fnExpr.pexp_loc ->
932+
(* Handle when the cursor is in the function expression itself. *)
933+
fnExpr
934+
|> completeExpr
935+
~completionContext:(completionContext |> CompletionContext.resetCtx)
911936
| Pexp_apply (fnExpr, args) -> (
912-
if locHasPos fnExpr.pexp_loc then
913-
(* someFn<com>(). Cursor in the function expression itself. *)
914-
completeExpr
915-
~completionContext:(CompletionContext.resetCtx completionContext)
916-
fnExpr
917-
else
918-
(* Check if the args has the cursor. *)
919-
(* Keep track of the positions of unlabelled arguments. *)
920-
let unlabelledArgPos = ref (-1) in
921-
let fnContextPath = exprToContextPath fnExpr in
922-
let argWithCursorInExpr =
923-
args
924-
|> List.find_opt
925-
(fun ((arg, argExpr) : Asttypes.arg_label * Parsetree.expression)
926-
->
927-
if arg = Nolabel then unlabelledArgPos := !unlabelledArgPos + 1;
928-
locHasPos argExpr.pexp_loc)
937+
(* Handle when the cursor isn't in the function expression. Possibly in an argument. *)
938+
(* TODO: Are we moving into all expressions we need here? The fn expression itself? *)
939+
let fnContextPath = exprToContextPath fnExpr in
940+
match fnContextPath with
941+
| None -> None
942+
| Some functionContextPath -> (
943+
let beforeCursor = completionContext.positionContext.beforeCursor in
944+
let isPipedExpr = false (* TODO: Implement *) in
945+
let args = extractExpApplyArgs ~args in
946+
let endPos = Loc.end_ expr.pexp_loc in
947+
let posAfterFnExpr = Loc.end_ fnExpr.pexp_loc in
948+
let fnHasCursor =
949+
posAfterFnExpr <= beforeCursor && beforeCursor < endPos
950+
in
951+
(* All of the labels already written in the application. *)
952+
let seenLabels =
953+
List.fold_right
954+
(fun arg seenLabels ->
955+
match arg with
956+
| {label = Some labelled} -> labelled.name :: seenLabels
957+
| {label = None} -> seenLabels)
958+
args []
929959
in
930-
(* TODO: Complete labelled argument names, pipes *)
931960
let makeCompletionContextWithArgumentLabel argumentLabel
932961
~functionContextPath =
933962
completionContext |> CompletionContext.resetCtx
934963
|> CompletionContext.currentlyExpectingOrReset
935964
(Some
936965
(Type (CFunctionArgument {functionContextPath; argumentLabel})))
937966
in
938-
match (argWithCursorInExpr, fnContextPath) with
939-
| None, _ -> None
940-
| Some (Nolabel, argExpr), Some functionContextPath ->
967+
(* Piped expressions always have an initial unlabelled argument. *)
968+
let unlabelledCount = ref (if isPipedExpr then 1 else 0) in
969+
let rec loop args =
970+
match args with
971+
| {label = Some labelled; exp} :: rest ->
972+
if labelled.posStart <= beforeCursor && beforeCursor < labelled.posEnd
973+
then
974+
(* Complete for a label: `someFn(~labelNam<com>)` *)
975+
CompletionResult.namedArg ~completionContext ~prefix:labelled.name
976+
~seenLabels ~functionContextPath
977+
else if locHasPos exp.pexp_loc then
978+
(* Completing in the assignment of labelled argument, with a value.
979+
`someFn(~someLabel=someIden<com>)` *)
980+
let completionContext =
981+
makeCompletionContextWithArgumentLabel (Labelled labelled.name)
982+
~functionContextPath
983+
in
984+
completeExpr ~completionContext exp
985+
else if CompletionExpressions.isExprHole exp then
986+
(* Completing in the assignment of labelled argument, with no value yet.
987+
The parser inserts an expr hole. `someFn(~someLabel=<com>)` *)
988+
let completionContext =
989+
makeCompletionContextWithArgumentLabel (Labelled labelled.name)
990+
~functionContextPath
991+
in
992+
CompletionResult.expression ~completionContext ~prefix:""
993+
else loop rest
994+
| {label = None; exp} :: rest ->
995+
if Res_parsetree_viewer.isTemplateLiteral exp then
996+
(* Ignore template literals, or we mess up completion inside of them. *)
997+
None
998+
else if locHasPos exp.pexp_loc then
999+
(* Completing in an unlabelled argument with a value. `someFn(someV<com>) *)
1000+
let completionContext =
1001+
makeCompletionContextWithArgumentLabel
1002+
(Unlabelled {argumentPosition = !unlabelledCount})
1003+
~functionContextPath
1004+
in
1005+
completeExpr ~completionContext exp
1006+
else if CompletionExpressions.isExprHole exp then
1007+
(* Completing in an unlabelled argument without a value. `someFn(true, <com>) *)
1008+
let completionContext =
1009+
makeCompletionContextWithArgumentLabel
1010+
(Unlabelled {argumentPosition = !unlabelledCount})
1011+
~functionContextPath
1012+
in
1013+
CompletionResult.expression ~completionContext ~prefix:""
1014+
else (
1015+
unlabelledCount := !unlabelledCount + 1;
1016+
loop rest)
1017+
| [] ->
1018+
if fnHasCursor then
1019+
(* No matches, but we know we have the cursor. Check the first char
1020+
behind the cursor. '~' means label completion. *)
1021+
match completionContext.positionContext.charBeforeCursor with
1022+
| Some '~' ->
1023+
CompletionResult.namedArg ~completionContext ~prefix:""
1024+
~seenLabels ~functionContextPath
1025+
| _ ->
1026+
(* No '~'. Assume we want to complete for the next unlabelled argument. *)
1027+
let completionContext =
1028+
makeCompletionContextWithArgumentLabel
1029+
(Unlabelled {argumentPosition = !unlabelledCount})
1030+
~functionContextPath
1031+
in
1032+
CompletionResult.expression ~completionContext ~prefix:""
1033+
else None
1034+
in
1035+
match args with
1036+
(* Special handling for empty fn calls, e.g. `let _ = someFn(<com>)` *)
1037+
| [
1038+
{
1039+
label = None;
1040+
exp = {pexp_desc = Pexp_construct ({txt = Lident "()"}, _)};
1041+
};
1042+
]
1043+
when fnHasCursor ->
9411044
let completionContext =
9421045
makeCompletionContextWithArgumentLabel
943-
(Unlabelled {argumentPosition = !unlabelledArgPos})
944-
~functionContextPath
945-
in
946-
completeExpr ~completionContext argExpr
947-
| Some (Labelled label, argExpr), Some functionContextPath ->
948-
let completionContext =
949-
makeCompletionContextWithArgumentLabel (Labelled label)
1046+
(Unlabelled {argumentPosition = 0})
9501047
~functionContextPath
9511048
in
952-
completeExpr ~completionContext argExpr
953-
| Some (Optional label, argExpr), Some functionContextPath ->
954-
let completionContext =
955-
makeCompletionContextWithArgumentLabel (Optional label)
956-
~functionContextPath
957-
in
958-
completeExpr ~completionContext argExpr
959-
| _ -> None)
1049+
CompletionResult.expression ~completionContext ~prefix:""
1050+
| _ -> loop args))
9601051
| Pexp_fun _ ->
9611052
(* We've found a function definition, like `let whatever = (someStr: string) => {}` *)
9621053
let rec loopFnExprs ~(completionContext : CompletionContext.t)

analysis/src/new-completions-todo.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
- Figure out location for completeExpr - when do we need to check locs, and when not?
44
- Pipe PPX transform, should we run it ourselves in the editor tooling?
5+
- Is there a practical difference btween Cexpression and Cpath?

analysis/tests/src/CompletionNew.res

+13-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type nestedRecord = {
2323
maybeVariant?: someVariant,
2424
}
2525

26-
type someRecord = {nested: option<nestedRecord>, variant: someVariant}
26+
type someRecord = {nested: option<nestedRecord>, variant: someVariant, someString: string}
2727

2828
// let myFunc: someRecord = {}
2929
// ^co2
@@ -127,3 +127,15 @@ type fn = (~name: string=?, string) => bool
127127

128128
// let x = foo->M
129129
// ^co2
130+
131+
// == Function arguments ==
132+
133+
let someFun = (~firstLabel, ~secondLabel=?, r: someRecord) => {
134+
firstLabel ++ secondLabel->Belt.Option.getWithDefault("") ++ r.someString
135+
}
136+
137+
// let ff = someFun(~secondLabel, ~f)
138+
// ^co2
139+
140+
// let ff = someFun(~secondLabel, ~f)
141+
// ^co2

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,9 @@ Scope: 0 items
154154
Looking for type: Type<CFunctionArgument CId(Value)=someFunc(~labelledArg)>
155155

156156
Complete2 src/CompletionNew.res 117:33
157-
No completions
157+
Result: Cexpression: ctxPath: , rootType: Type<CFunctionArgument CId(Value)=someFunc(~labelledArg)>
158+
Scope: 0 items
159+
Looking for type: Type<CFunctionArgument CId(Value)=someFunc(~labelledArg)>
158160

159161
Complete2 src/CompletionNew.res 121:17
160162
Result: CtxPath: (CId(Value)=foo)->id
@@ -171,3 +173,13 @@ Result: Cexpression: ctxPath: CId(Module)=M, rootType: Unit, prefix: "M"
171173
Scope: 0 items
172174
Looking for type: Unit
173175

176+
Complete2 src/CompletionNew.res 136:36
177+
Result: CnamedArg(CId(Value)=someFun, f, [secondLabel, f])
178+
Scope: 0 items
179+
Looking for type: TypeAtLoc: [136:7->136:9]
180+
181+
Complete2 src/CompletionNew.res 139:37
182+
Result: Cexpression: ctxPath: , rootType: Type<CFunctionArgument CId(Value)=someFun($0)>
183+
Scope: 0 items
184+
Looking for type: Type<CFunctionArgument CId(Value)=someFun($0)>
185+

0 commit comments

Comments
 (0)