Skip to content

Commit 693166a

Browse files
committed
handle completing new items in constructor/variant payloads with multiple items
1 parent c3ea53b commit 693166a

File tree

3 files changed

+299
-55
lines changed

3 files changed

+299
-55
lines changed

analysis/src/CompletionFrontEnd.ml

Lines changed: 111 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ let isPatternHole pat =
1818
| Ppat_extension ({txt = "rescript.patternhole"}, _) -> true
1919
| _ -> false
2020

21+
let isPatternTuple pat =
22+
match pat.Parsetree.ppat_desc with
23+
| Ppat_tuple _ -> true
24+
| _ -> false
25+
2126
type prop = {
2227
name: string;
2328
posStart: int * int;
@@ -233,6 +238,12 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
233238
})
234239
| _ -> loop args
235240

241+
let lastLocIndexBeforePos locs ~pos =
242+
let posNum = ref (-1) in
243+
locs
244+
|> List.iteri (fun index loc -> if pos >= Loc.start loc then posNum := index);
245+
if !posNum > -1 then Some !posNum else None
246+
236247
let rec exprToContextPath (e : Parsetree.expression) =
237248
match e.pexp_desc with
238249
| Pexp_constant (Pconst_string _) -> Some Completable.CPString
@@ -504,26 +515,31 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
504515
when ppat_loc
505516
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
506517
= HasCursor ->
507-
(* Empty payload *)
518+
(* Empty payload with cursor, like: Test(<com>) *)
508519
Some
509520
( "",
510521
[
511522
Completable.PVariantPayload
512523
{constructorName = getUnqualifiedName txt; itemNum = 0};
513524
]
514525
@ patternPath )
515-
| Ppat_construct
516-
( {txt},
517-
Some
518-
({
519-
ppat_loc;
520-
ppat_desc =
521-
( Ppat_var _ | Ppat_record _ | Ppat_construct _
522-
| Ppat_variant _ );
523-
} as pat) )
524-
when ppat_loc
526+
| Ppat_construct ({txt}, Some pat)
527+
when posBeforeCursor >= (pat.ppat_loc |> Loc.end_)
528+
&& firstCharBeforeCursorNoWhite = Some ','
529+
&& isPatternTuple pat = false ->
530+
(* Empty payload with trailing ',', like: Test(true, <com>) *)
531+
Some
532+
( "",
533+
[
534+
Completable.PVariantPayload
535+
{constructorName = getUnqualifiedName txt; itemNum = 1};
536+
]
537+
@ patternPath )
538+
| Ppat_construct ({txt}, Some pat)
539+
when pat.ppat_loc
525540
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
526-
= HasCursor ->
541+
= HasCursor
542+
&& isPatternTuple pat = false ->
527543
(* Single payload *)
528544
pat
529545
|> traversePattern
@@ -537,51 +553,75 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
537553
({txt}, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems})
538554
when ppat_loc
539555
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
540-
= HasCursor ->
541-
(* Multiple payloads with cursor in item *)
542-
(* TODO: New item with comma *)
556+
= HasCursor -> (
543557
let itemNum = ref (-1) in
544-
tupleItems
545-
|> List.find_map (fun pat ->
546-
itemNum := !itemNum + 1;
547-
pat
548-
|> traversePattern
549-
~patternPath:
550-
([
551-
Completable.PVariantPayload
552-
{
553-
constructorName = getUnqualifiedName txt;
554-
itemNum = !itemNum;
555-
};
556-
]
557-
@ patternPath))
558+
let itemWithCursor =
559+
tupleItems
560+
|> List.find_map (fun pat ->
561+
itemNum := !itemNum + 1;
562+
pat
563+
|> traversePattern
564+
~patternPath:
565+
([
566+
Completable.PVariantPayload
567+
{
568+
constructorName = getUnqualifiedName txt;
569+
itemNum = !itemNum;
570+
};
571+
]
572+
@ patternPath))
573+
in
574+
match (itemWithCursor, firstCharBeforeCursorNoWhite) with
575+
| None, Some ',' -> (
576+
(* No tuple item has the cursor, but there's a comma before the cursor.
577+
Figure out what arg we're trying to complete. Example: Test(true, <com>, None) *)
578+
let locs = tupleItems |> List.map (fun p -> p.Parsetree.ppat_loc) in
579+
match locs |> lastLocIndexBeforePos ~pos:posBeforeCursor with
580+
| None -> None
581+
| Some itemNum ->
582+
Some
583+
( "",
584+
[
585+
Completable.PVariantPayload
586+
{
587+
constructorName = getUnqualifiedName txt;
588+
itemNum = itemNum + 1;
589+
};
590+
]
591+
@ patternPath ))
592+
| v, _ -> v)
558593
| Ppat_variant
559594
( txt,
560595
Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)}
561596
)
562597
when ppat_loc
563598
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
564599
= HasCursor ->
565-
(* Empty payload *)
600+
(* Empty payload with cursor, like: #test(<com>) *)
566601
Some
567602
( "",
568603
[
569604
Completable.PPolyvariantPayload
570605
{constructorName = txt; itemNum = 0};
571606
]
572607
@ patternPath )
573-
| Ppat_variant
574-
( txt,
575-
Some
576-
({
577-
ppat_loc;
578-
ppat_desc =
579-
( Ppat_var _ | Ppat_record _ | Ppat_construct _
580-
| Ppat_variant _ );
581-
} as pat) )
582-
when ppat_loc
608+
| Ppat_variant (txt, Some pat)
609+
when posBeforeCursor >= (pat.ppat_loc |> Loc.end_)
610+
&& firstCharBeforeCursorNoWhite = Some ','
611+
&& isPatternTuple pat = false ->
612+
(* Empty payload with trailing ',', like: #test(true, <com>) *)
613+
Some
614+
( "",
615+
[
616+
Completable.PPolyvariantPayload
617+
{constructorName = txt; itemNum = 1};
618+
]
619+
@ patternPath )
620+
| Ppat_variant (txt, Some pat)
621+
when pat.ppat_loc
583622
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
584-
= HasCursor ->
623+
= HasCursor
624+
&& isPatternTuple pat = false ->
585625
(* Single payload *)
586626
pat
587627
|> traversePattern
@@ -594,21 +634,37 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
594634
| Ppat_variant (txt, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems})
595635
when ppat_loc
596636
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
597-
= HasCursor ->
598-
(* Multiple payloads with cursor in item *)
599-
(* TODO: New item with comma *)
637+
= HasCursor -> (
600638
let itemNum = ref (-1) in
601-
tupleItems
602-
|> List.find_map (fun pat ->
603-
itemNum := !itemNum + 1;
604-
pat
605-
|> traversePattern
606-
~patternPath:
607-
([
608-
Completable.PPolyvariantPayload
609-
{constructorName = txt; itemNum = !itemNum};
610-
]
611-
@ patternPath))
639+
let itemWithCursor =
640+
tupleItems
641+
|> List.find_map (fun pat ->
642+
itemNum := !itemNum + 1;
643+
pat
644+
|> traversePattern
645+
~patternPath:
646+
([
647+
Completable.PPolyvariantPayload
648+
{constructorName = txt; itemNum = !itemNum};
649+
]
650+
@ patternPath))
651+
in
652+
match (itemWithCursor, firstCharBeforeCursorNoWhite) with
653+
| None, Some ',' -> (
654+
(* No tuple item has the cursor, but there's a comma before the cursor.
655+
Figure out what arg we're trying to complete. Example: #test(true, <com>, None) *)
656+
let locs = tupleItems |> List.map (fun p -> p.Parsetree.ppat_loc) in
657+
match locs |> lastLocIndexBeforePos ~pos:posBeforeCursor with
658+
| None -> None
659+
| Some itemNum ->
660+
Some
661+
( "",
662+
[
663+
Completable.PPolyvariantPayload
664+
{constructorName = txt; itemNum = itemNum + 1};
665+
]
666+
@ patternPath ))
667+
| v, _ -> v)
612668
| _ -> None
613669
else None
614670
in

analysis/tests/src/CompletionPattern.res

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,35 @@ ignore(o)
127127

128128
// switch o { | Some() }
129129
// ^com
130+
131+
type multiPayloadVariant = Test(int, bool, option<bool>, array<bool>)
132+
133+
let p = Test(1, true, Some(false), [])
134+
135+
// switch p { | Test(1, )}
136+
// ^com
137+
138+
// switch p { | Test(1, true, )}
139+
// ^com
140+
141+
// switch p { | Test(1, , None)}
142+
// ^com
143+
144+
// switch p { | Test(1, true, None, )}
145+
// ^com
146+
147+
type multiPayloadPolyVariant = [#test(int, bool, option<bool>, array<bool>)]
148+
149+
let v: multiPayloadPolyVariant = #test(1, true, Some(false), [])
150+
151+
// switch v { | #test(1, )}
152+
// ^com
153+
154+
// switch v { | #test(1, true, )}
155+
// ^com
156+
157+
// switch v { | #test(1, , None)}
158+
// ^com
159+
160+
// switch v { | #test(1, true, None, )}
161+
// ^com

0 commit comments

Comments
 (0)