60
60
61
61
type completionCategory = Type | Value | Module | Field
62
62
63
+ type argumentLabel =
64
+ | Unlabelled of {argumentPosition : int }
65
+ | Labelled of string
66
+ | Optional of string
67
+
63
68
type ctxPath =
64
69
| CUnknown (* * Something that cannot be resolved right now *)
65
70
| CId of string list * completionCategory
@@ -87,6 +92,15 @@ type ctxPath =
87
92
and the string is the name of the property we're accessing. *)
88
93
| CApply of ctxPath * Asttypes .arg_label list
89
94
(* * Function application. `someFunction(someVar, ~otherLabel="hello")`. The ctxPath points to the function. *)
95
+ | CFunctionArgument of {
96
+ functionContextPath : ctxPath ;
97
+ argumentLabel : argumentLabel ;
98
+ } (* * A function argument, either labelled or unlabelled.*)
99
+ | CPipe of {
100
+ ctxPath : ctxPath ; (* * Context path to the function being called. *)
101
+ id : string ;
102
+ lhsLoc : Location .t ; (* * Location of the left hand side. *)
103
+ } (* * Piped call. `foo->someFn`. *)
90
104
91
105
let rec ctxPathToString (ctxPath : ctxPath ) =
92
106
match ctxPath with
@@ -130,6 +144,16 @@ let rec ctxPathToString (ctxPath : ctxPath) =
130
144
(match ctxPath with
131
145
| None -> " "
132
146
| Some ctxPath -> " <" ^ ctxPathToString ctxPath ^ " >" )
147
+ | CFunctionArgument {functionContextPath; argumentLabel} ->
148
+ " CFunctionArgument "
149
+ ^ (functionContextPath |> ctxPathToString)
150
+ ^ " ("
151
+ ^ (match argumentLabel with
152
+ | Unlabelled {argumentPosition} -> " $" ^ string_of_int argumentPosition
153
+ | Labelled name -> " ~" ^ name
154
+ | Optional name -> " ~" ^ name ^ " =?" )
155
+ ^ " )"
156
+ | CPipe {ctxPath; id} -> " (" ^ ctxPathToString ctxPath ^ " )->" ^ id
133
157
134
158
type currentlyExpecting =
135
159
| Unit (* * Unit, (). Is what we reset to. *)
@@ -437,6 +461,37 @@ let checkIfPatternHoleEmptyCursor ~(completionContext : CompletionContext.t)
437
461
~pos: completionContext.positionContext.beforeCursor
438
462
= EmptyLoc
439
463
464
+ let completePipeChain (exp : Parsetree.expression ) =
465
+ (* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe,
466
+ so it can be completed.
467
+ Example:
468
+ someArray->Js.Array2.filter(v => v > 10)->Js.Array2.map(v => v + 2)->
469
+ will complete as:
470
+ Js.Array2.map(someArray->Js.Array2.filter(v => v > 10), v => v + 2)->
471
+ *)
472
+ match exp.pexp_desc with
473
+ (* When the left side of the pipe we're completing is a function application.
474
+ Example: someArray->Js.Array2.map(v => v + 2)-> *)
475
+ | Pexp_apply
476
+ ( {pexp_desc = Pexp_ident {txt = Lident (" |." | " |.u" )}},
477
+ [_; (_, {pexp_desc = Pexp_apply (d, _)})] ) ->
478
+ exprToContextPath exp |> Option. map (fun ctxPath -> (ctxPath, d.pexp_loc))
479
+ (* When the left side of the pipe we're completing is an identifier application.
480
+ Example: someArray->filterAllTheGoodStuff-> *)
481
+ | Pexp_apply
482
+ ( {pexp_desc = Pexp_ident {txt = Lident (" |." | " |.u" )}},
483
+ [_; (_, {pexp_desc = Pexp_ident _; pexp_loc})] ) ->
484
+ exprToContextPath exp |> Option. map (fun ctxPath -> (ctxPath, pexp_loc))
485
+ | _ -> None
486
+
487
+ let completePipe ~id (lhs : Parsetree.expression ) =
488
+ match completePipeChain lhs with
489
+ | Some (pipe , lhsLoc ) -> Some (CPipe {ctxPath = pipe; id; lhsLoc})
490
+ | None -> (
491
+ match exprToContextPath lhs with
492
+ | Some pipe -> Some (CPipe {ctxPath = pipe; id; lhsLoc = lhs.pexp_loc})
493
+ | None -> None )
494
+
440
495
(* * Scopes *)
441
496
let rec scopePattern ~scope (pat : Parsetree.pattern ) =
442
497
match pat.ppat_desc with
@@ -554,40 +609,52 @@ and completeValueBinding ~(completionContext : CompletionContext.t)
554
609
completionContext
555
610
in
556
611
completePattern ~completion Context vb.pvb_pat
557
- else if locHasPos vb.pvb_expr.pexp_loc then
612
+ else if locHasPos vb.pvb_loc then
613
+ (* First try completing the expression. *)
558
614
(* A let binding expression either has the constraint of the binding,
559
615
or an inferred constraint (if it has been compiled), or no constraint. *)
560
- let completionContext =
616
+ let completionContextForExprCompletion =
561
617
completionContext
562
618
|> CompletionContext. currentlyExpectingOrTypeAtLoc
563
619
~loc: vb.pvb_pat.ppat_loc bindingConstraint
564
620
in
565
- completeExpr ~completion Context vb.pvb_expr
566
- else if locHasPos vb.pvb_loc then
567
- (* In the binding but not in the pattern or expression means parser error recovery.
568
- We can still complete the pattern or expression if we have enough information. *)
569
- let exprHole = checkIfExprHoleEmptyCursor ~completion Context vb.pvb_expr in
570
- let patHole = checkIfPatternHoleEmptyCursor ~completion Context vb.pvb_pat in
571
- let exprCtxPath = exprToContextPath vb.pvb_expr in
572
- (* Try the expression. Example: `let someVar: someType = <com> *)
573
- if exprHole then
574
- let completionContext =
575
- completionContext
576
- |> CompletionContext. currentlyExpectingOrTypeAtLoc
577
- ~loc: vb.pvb_pat.ppat_loc bindingConstraint
621
+ let completedExpression =
622
+ completeExpr ~completion Context:completionContextForExprCompletion
623
+ vb.pvb_expr
624
+ in
625
+ match completedExpression with
626
+ | Some res -> Some res
627
+ | None ->
628
+ (* In the binding but not in the pattern or expression means parser error recovery.
629
+ We can still complete the pattern or expression if we have enough information. *)
630
+ let exprHole =
631
+ checkIfExprHoleEmptyCursor
632
+ ~completion Context:completionContextForExprCompletion vb.pvb_expr
578
633
in
579
- CompletionResult. ctxPath (CId ([] , Value )) completionContext
580
- else if patHole then
581
- let completionContext =
582
- CompletionContext. currentlyExpectingOrTypeAtLoc
583
- ~loc: vb.pvb_expr.pexp_loc
584
- (match exprCtxPath with
585
- | None -> None
586
- | Some ctxPath -> Some (Type ctxPath))
587
- completionContext
634
+ let patHole =
635
+ checkIfPatternHoleEmptyCursor
636
+ ~completion Context:completionContextForExprCompletion vb.pvb_pat
588
637
in
589
- CompletionResult. pattern ~prefix: " " ~completion Context
590
- else None
638
+ let exprCtxPath = exprToContextPath vb.pvb_expr in
639
+ (* Try the expression. Example: `let someVar: someType = <com> *)
640
+ if exprHole then
641
+ let completionContext =
642
+ completionContextForExprCompletion
643
+ |> CompletionContext. currentlyExpectingOrTypeAtLoc
644
+ ~loc: vb.pvb_pat.ppat_loc bindingConstraint
645
+ in
646
+ CompletionResult. ctxPath (CId ([] , Value )) completionContext
647
+ else if patHole then
648
+ let completionContext =
649
+ CompletionContext. currentlyExpectingOrTypeAtLoc
650
+ ~loc: vb.pvb_expr.pexp_loc
651
+ (match exprCtxPath with
652
+ | None -> None
653
+ | Some ctxPath -> Some (Type ctxPath))
654
+ completionContextForExprCompletion
655
+ in
656
+ CompletionResult. pattern ~prefix: " " ~completion Context
657
+ else None
591
658
else None
592
659
593
660
and completeValueBindings ~(completionContext : CompletionContext.t )
@@ -605,6 +672,7 @@ and completeValueBindings ~(completionContext : CompletionContext.t)
605
672
|> Utils. findMap (fun (vb : Parsetree.value_binding ) ->
606
673
completeValueBinding ~completion Context vb)
607
674
675
+ (* * Completes an expression. Designed to run without pre-checking if the cursor is in the expression. *)
608
676
and completeExpr ~completionContext (expr : Parsetree.expression ) :
609
677
CompletionResult. t =
610
678
let locHasPos = completionContext.positionContext.locHasPos in
@@ -689,15 +757,14 @@ and completeExpr ~completionContext (expr : Parsetree.expression) :
689
757
CompletionResult. ctxPath
690
758
(CId (flattenLidCheckDot ~completion Context fieldName, Value ))
691
759
completionContext
692
- else if locHasPos fieldExpr.pexp_loc then
760
+ else
693
761
completeExpr
694
762
~completion Context:
695
763
(CompletionContext. addCtxPathItem
696
764
(CRecordField
697
765
{prefix = fieldName.txt |> Longident. last; seenFields})
698
766
completionContext)
699
- fieldExpr
700
- else None )
767
+ fieldExpr)
701
768
in
702
769
match fieldToComplete with
703
770
| None -> (
@@ -795,13 +862,96 @@ and completeExpr ~completionContext (expr : Parsetree.expression) :
795
862
else if locHasPos nextExpr.pexp_loc then
796
863
completeExpr ~completion Context nextExpr
797
864
else None
798
- | Pexp_apply (fnExpr , _args ) ->
865
+ (* == Pipes == *)
866
+ | Pexp_apply
867
+ ( {pexp_desc = Pexp_ident {txt = Lident (" |." | " |.u" ); loc = opLoc}},
868
+ [
869
+ (_, lhs);
870
+ (_, {pexp_desc = Pexp_extension _; pexp_loc = {loc_ghost = true }});
871
+ ] )
872
+ when locHasPos opLoc -> (
873
+ (* Case foo-> when the parser adds a ghost expression to the rhs
874
+ so the apply expression does not include the cursor *)
875
+ match completePipe lhs ~id: " " with
876
+ | None -> None
877
+ | Some cpipe ->
878
+ completionContext |> CompletionContext. withResetCtx
879
+ |> CompletionResult. ctxPath cpipe)
880
+ | Pexp_apply
881
+ ( {pexp_desc = Pexp_ident {txt = Lident (" |." | " |.u" )}},
882
+ [
883
+ (_, lhs);
884
+ (_, {pexp_desc = Pexp_ident {txt = Longident. Lident id; loc}});
885
+ ] )
886
+ when locHasPos loc -> (
887
+ (* foo->id *)
888
+ match completePipe lhs ~id with
889
+ | None -> None
890
+ | Some cpipe ->
891
+ completionContext |> CompletionContext. withResetCtx
892
+ |> CompletionResult. ctxPath cpipe)
893
+ | Pexp_apply
894
+ ( {pexp_desc = Pexp_ident {txt = Lident (" |." | " |.u" ); loc = opLoc}},
895
+ [(_, lhs); _] )
896
+ when Loc. end_ opLoc = completionContext.positionContext.cursor -> (
897
+ match completePipe lhs ~id: " " with
898
+ | None -> None
899
+ | Some cpipe ->
900
+ completionContext |> CompletionContext. withResetCtx
901
+ |> CompletionResult. ctxPath cpipe)
902
+ | Pexp_apply ({pexp_desc = Pexp_ident {txt = Lident (" |." | " |.u" )}}, [_; _])
903
+ ->
904
+ (* Ignore any other pipe. *)
905
+ None
906
+ | Pexp_apply (fnExpr , args ) -> (
799
907
if locHasPos fnExpr.pexp_loc then
908
+ (* someFn<com>(). Cursor in the function expression itself. *)
800
909
completeExpr
801
910
~completion Context:(CompletionContext. withResetCtx completionContext)
802
911
fnExpr
803
- else (* TODO: Complete args. Pipes *)
804
- None
912
+ else
913
+ (* Check if the args has the cursor. *)
914
+ (* Keep track of the positions of unlabelled arguments. *)
915
+ let unlabelledArgPos = ref (- 1 ) in
916
+ let fnContextPath = exprToContextPath fnExpr in
917
+ let argWithCursorInExpr =
918
+ args
919
+ |> List. find_opt
920
+ (fun ((arg , argExpr ) : Asttypes. arg_label * Parsetree. expression )
921
+ ->
922
+ if arg = Nolabel then unlabelledArgPos := ! unlabelledArgPos + 1 ;
923
+ locHasPos argExpr.pexp_loc)
924
+ in
925
+ (* TODO: Complete labelled argument names, pipes *)
926
+ let makeCompletionContextWithArgumentLabel argumentLabel
927
+ ~functionContextPath =
928
+ completionContext |> CompletionContext. withResetCtx
929
+ |> CompletionContext. currentlyExpectingOrReset
930
+ (Some
931
+ (Type (CFunctionArgument {functionContextPath; argumentLabel})))
932
+ in
933
+ match (argWithCursorInExpr, fnContextPath) with
934
+ | None , _ -> None
935
+ | Some (Nolabel, argExpr ), Some functionContextPath ->
936
+ let completionContext =
937
+ makeCompletionContextWithArgumentLabel
938
+ (Unlabelled {argumentPosition = ! unlabelledArgPos})
939
+ ~function ContextPath
940
+ in
941
+ completeExpr ~completion Context argExpr
942
+ | Some (Labelled label , argExpr ), Some functionContextPath ->
943
+ let completionContext =
944
+ makeCompletionContextWithArgumentLabel (Labelled label)
945
+ ~function ContextPath
946
+ in
947
+ completeExpr ~completion Context argExpr
948
+ | Some (Optional label , argExpr ), Some functionContextPath ->
949
+ let completionContext =
950
+ makeCompletionContextWithArgumentLabel (Optional label)
951
+ ~function ContextPath
952
+ in
953
+ completeExpr ~completion Context argExpr
954
+ | _ -> None )
805
955
| Pexp_fun _ ->
806
956
(* We've found a function definition, like `let whatever = (someStr: string) => {}` *)
807
957
let rec loopFnExprs ~(completionContext : CompletionContext.t )
0 commit comments