@@ -15,18 +15,69 @@ let isPatternTuple pat =
1515  |  Ppat_tuple  _  -> true 
1616  |  _  -> false 
1717
18- let  traverseExpr  (exp  : Parsetree.expression ) ~exprPath   ~pos   = 
18+ let  rec  traverseExpr  (exp  : Parsetree.expression ) ~exprPath   ~pos  
19+     ~firstCharBeforeCursorNoWhite   = 
1920  let  someIfHasCursor  v  = 
2021    if  exp.pexp_loc |>  CursorPosition. locHasCursor ~pos  then  Some  v else  None 
2122  in 
2223  match  exp.pexp_desc with 
24+   |  Pexp_ident  {txt  = Lident  txt }  when  Utils. hasBraces exp.pexp_attributes ->
25+     (*  An ident with braces attribute corresponds to for example `{n}`.
26+        Looks like a record but is parsed as an ident with braces. *)  
27+     someIfHasCursor (txt, [Completable. ERecordBody  {seenFields =  [] }] @  exprPath)
2328  |  Pexp_ident  {txt  = Lident  txt }  -> someIfHasCursor (txt, exprPath)
2429  |  Pexp_construct  ({txt  = Lident  "()" } , _ ) -> someIfHasCursor (" "  , exprPath)
2530  |  Pexp_construct  ({txt  = Lident  txt } , None) -> someIfHasCursor (txt, exprPath)
2631  |  Pexp_variant  (label , None) -> someIfHasCursor (" #"   ^  label, exprPath)
2732  |  Pexp_record  ([] , _ ) ->
2833    (*  Empty fields means we're in a record body `{}`. Complete for the fields. *) 
2934    someIfHasCursor (" "  , [Completable. ERecordBody  {seenFields =  [] }] @  exprPath)
35+   |  Pexp_record  (fields , _ ) -> (
36+     let  fieldWithCursor =  ref  None  in 
37+     let  fieldWithExprHole =  ref  None  in 
38+     fields
39+     |>  List. iter (fun  (fname , exp ) ->
40+            match 
41+              ( fname.Location. txt,
42+                exp.Parsetree. pexp_loc |>  CursorPosition. classifyLoc ~pos  )
43+            with 
44+            |  Longident. Lident  fname , HasCursor  ->
45+              fieldWithCursor :=  Some  (fname, exp)
46+            |  Lident  fname , _  when  isExprHole exp ->
47+              fieldWithExprHole :=  Some  (fname, exp)
48+            |  _  -> () );
49+     let  seenFields = 
50+       fields
51+       |>  List. filter_map (fun  (fieldName , _f ) ->
52+              match  fieldName with 
53+              |  {Location. txt  = Longident. Lident  fieldName }  -> Some  fieldName
54+              |  _  -> None )
55+     in 
56+     match  (! fieldWithCursor, ! fieldWithExprHole) with 
57+     |  Some  (fname , f ), _  |  None , Some  (fname , f ) -> (
58+       match  f.pexp_desc with 
59+       |  Pexp_extension  ({txt  = "rescript.exprhole" } , _ ) ->
60+         (*  An expression hole means for example `{someField: <com>}`. We want to complete for the type of `someField`.  *) 
61+         someIfHasCursor
62+           (" "  , [Completable. EFollowRecordField  {fieldName =  fname}] @  exprPath)
63+       |  Pexp_ident  {txt  = Lident  txt }  ->
64+         (*  A var means `{s}` or similar. Complete for fields. *) 
65+         someIfHasCursor (txt, [Completable. ERecordBody  {seenFields}] @  exprPath)
66+       |  _  ->
67+         f
68+         |>  traverseExpr ~first CharBeforeCursorNoWhite ~pos 
69+              ~expr Path:
70+                ([Completable. EFollowRecordField  {fieldName =  fname}] @  exprPath)
71+       )
72+     |  None , None  -> (
73+       (*  Figure out if we're completing for a new field.
74+          If the cursor is inside of the record body, but no field has the cursor, 
75+          and there's no pattern hole. Check the first char to the left of the cursor, 
76+          ignoring white space. If that's a comma, we assume you're completing for a new field. *)  
77+       match  firstCharBeforeCursorNoWhite with 
78+       |  Some  ','  ->
79+         someIfHasCursor (" "  , [Completable. ERecordBody  {seenFields}] @  exprPath)
80+       |  _  -> None ))
3081  |  _  -> None 
3182
3283type  prop  = {
@@ -42,8 +93,8 @@ type jsxProps = {
4293  childrenStart : (int  *  int ) option ; 
4394}
4495
45- let  findJsxPropsCompletable  ~jsxProps   ~endPos   ~posBeforeCursor    ~ posAfterCompName 
46-     = 
96+ let  findJsxPropsCompletable  ~jsxProps   ~endPos   ~posBeforeCursor  
97+     ~ firstCharBeforeCursorNoWhite   ~ posAfterCompName   = 
4798  let  allLabels = 
4899    List. fold_right
49100      (fun  prop  allLabels  -> prop.name :: allLabels)
@@ -66,7 +117,10 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName
66117        None 
67118      else  if  prop.exp.pexp_loc |>  Loc. hasPos ~pos: posBeforeCursor then 
68119        (*  Cursor on expr assigned *) 
69-         match  traverseExpr prop.exp ~expr Path:[]  ~pos: posBeforeCursor with 
120+         match 
121+           traverseExpr prop.exp ~expr Path:[]  ~pos: posBeforeCursor
122+             ~first CharBeforeCursorNoWhite
123+         with 
70124        |  Some  (prefix , nested ) ->
71125          Some 
72126            (CjsxPropValue 
@@ -148,8 +202,8 @@ let extractJsxProps ~(compName : Longident.t Location.loc) ~args =
148202  args |>  processProps ~acc: [] 
149203
150204let  findArgCompletables  ~(args  : arg list ) ~endPos   ~posBeforeCursor  
151-     ~(contextPath  : Completable.contextPath ) ~posAfterFunExpr    ~ charBeforeCursor 
152-     ~isPipedExpr   = 
205+     ~(contextPath  : Completable.contextPath ) ~posAfterFunExpr  
206+     ~firstCharBeforeCursorNoWhite    ~ charBeforeCursor   ~ isPipedExpr   = 
153207  let  fnHasCursor = 
154208    posAfterFunExpr < =  posBeforeCursor &&  posBeforeCursor <  endPos
155209  in 
@@ -171,7 +225,10 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
171225      then  Some  (Completable. CnamedArg  (contextPath, labelled.name, allNames))
172226      else  if  exp.pexp_loc |>  Loc. hasPos ~pos: posBeforeCursor then 
173227        (*  Completing in the assignment of labelled argument *) 
174-         match  traverseExpr exp ~expr Path:[]  ~pos: posBeforeCursor with 
228+         match 
229+           traverseExpr exp ~expr Path:[]  ~pos: posBeforeCursor
230+             ~first CharBeforeCursorNoWhite
231+         with 
175232        |  None  -> None 
176233        |  Some  (prefix , nested ) ->
177234          Some 
@@ -196,7 +253,10 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
196253      if  Res_parsetree_viewer. isTemplateLiteral exp then  None 
197254      else  if  exp.pexp_loc |>  Loc. hasPos ~pos: posBeforeCursor then 
198255        (*  Completing in an unlabelled argument *) 
199-         match  traverseExpr exp ~pos: posBeforeCursor ~expr Path:[]  with 
256+         match 
257+           traverseExpr exp ~pos: posBeforeCursor ~first CharBeforeCursorNoWhite
258+             ~expr Path:[] 
259+         with 
200260        |  None  -> None 
201261        |  Some  (prefix , nested ) ->
202262          Some 
@@ -972,6 +1032,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
9721032          let  jsxCompletable = 
9731033            findJsxPropsCompletable ~jsx Props ~end Pos:(Loc. end_ expr.pexp_loc)
9741034              ~pos BeforeCursor ~pos AfterCompName:(Loc. end_ compName.loc)
1035+               ~first CharBeforeCursorNoWhite
9751036          in 
9761037          if  jsxCompletable <>  None  then  setResultOpt jsxCompletable
9771038          else  if  compName.loc |>  Loc. hasPos ~pos: posBeforeCursor then 
@@ -1009,6 +1070,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
10091070                ~end Pos:(Loc. end_ expr.pexp_loc) ~pos BeforeCursor
10101071                ~pos AfterFunExpr:(Loc. end_ funExpr.pexp_loc)
10111072                ~char BeforeCursor ~is PipedExpr:true 
1073+                 ~first CharBeforeCursorNoWhite
10121074            |  None  -> None 
10131075          in 
10141076
@@ -1044,6 +1106,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
10441106                ~end Pos:(Loc. end_ expr.pexp_loc) ~pos BeforeCursor
10451107                ~pos AfterFunExpr:(Loc. end_ funExpr.pexp_loc)
10461108                ~char BeforeCursor ~is PipedExpr:false 
1109+                 ~first CharBeforeCursorNoWhite
10471110            |  None  -> None 
10481111          in 
10491112
0 commit comments