@@ -7,7 +7,7 @@ module PositionContext = struct
7
7
beforeCursor : Pos .t ; (* * The position just before the cursor *)
8
8
noWhitespace : Pos .t ;
9
9
(* * The position of the cursor, removing any whitespace _before_ it *)
10
- firstCharBeforeNoWhitespace : char option ;
10
+ charBeforeNoWhitespace : char option ;
11
11
(* * The first character before the cursor, excluding any whitespace *)
12
12
charBeforeCursor : char option ;
13
13
(* * The char before the cursor, not excluding whitespace *)
@@ -44,7 +44,7 @@ module PositionContext = struct
44
44
offset;
45
45
beforeCursor = posBeforeCursor;
46
46
noWhitespace = posNoWhite;
47
- firstCharBeforeNoWhitespace = firstCharBeforeCursorNoWhite;
47
+ charBeforeNoWhitespace = firstCharBeforeCursorNoWhite;
48
48
cursor = posCursor;
49
49
charBeforeCursor;
50
50
whitespaceAfterCursor;
@@ -60,8 +60,10 @@ type ctxPath =
60
60
(* * A variant payload. `Some(<com>)` = itemNum 0, `Whatever(true, f<com>)` = itemNum 1*)
61
61
| CRecordField of {seenFields : string list ; prefix : string }
62
62
(* * A record field. `let f = {on: true, s<com>}` seenFields = [on], prefix = "s",*)
63
+ | COption of ctxPath (* * An option with an inner type. *)
64
+ | CArray of ctxPath option (* * An array with an inner type. *)
63
65
64
- let ctxPathToString (ctxPath : ctxPath ) =
66
+ let rec ctxPathToString (ctxPath : ctxPath ) =
65
67
match ctxPath with
66
68
| CId (prefix , typ ) ->
67
69
Printf. sprintf " CId(%s)=%s"
@@ -73,16 +75,18 @@ let ctxPathToString (ctxPath : ctxPath) =
73
75
(ident prefix)
74
76
| CVariantPayload {itemNum} -> Printf. sprintf " CVariantPayload($%i)" itemNum
75
77
| CRecordField {prefix} -> Printf. sprintf " CRecordField=%s" prefix
78
+ | COption ctxPath -> Printf. sprintf " COption<%s>" (ctxPathToString ctxPath)
79
+ | CArray ctxPath ->
80
+ Printf. sprintf " CArray%s"
81
+ (match ctxPath with
82
+ | None -> " "
83
+ | Some ctxPath -> " [" ^ ctxPathToString ctxPath ^ " ]" )
76
84
77
- type currentlyExpecting = Type of Parsetree .core_type
85
+ type currentlyExpecting = Type of ctxPath
78
86
79
- type completionTypes =
80
- | CId of string list * completionCategory
81
- | CType of {
82
- pathToType : Parsetree .core_type ;
83
- prefix : string ; (* * What is already written, if anything *)
84
- }
85
- | CtxPath of ctxPath list
87
+ let currentlyExpectingToString (c : currentlyExpecting ) =
88
+ match c with
89
+ | Type ctxPath -> Printf. sprintf " Type<%s>" (ctxPathToString ctxPath)
86
90
87
91
type completionContext = {
88
92
positionContext : PositionContext .t ;
@@ -91,20 +95,7 @@ type completionContext = {
91
95
ctxPath : ctxPath list ;
92
96
}
93
97
94
- type completionResult = (completionTypes * completionContext ) option
95
-
96
- let findCurrentlyLookingForInPattern (pat : Parsetree.pattern ) =
97
- match pat.ppat_desc with
98
- | Ppat_constraint (_pat , typ ) -> Some (Type typ)
99
- | _ -> None
100
-
101
- let mergeCurrentlyLookingFor (currentlyExpecting : currentlyExpecting option )
102
- list =
103
- match currentlyExpecting with
104
- | None -> list
105
- | Some currentlyExpecting -> currentlyExpecting :: list
106
-
107
- let contextWithNewScope scope context = {context with scope}
98
+ type completionResult = (ctxPath list * completionContext ) option
108
99
109
100
let flattenLidCheckDot ?(jsx = true ) ~(completionContext : completionContext )
110
101
(lid : Longident.t Location.loc ) =
@@ -122,6 +113,36 @@ let flattenLidCheckDot ?(jsx = true) ~(completionContext : completionContext)
122
113
in
123
114
Utils. flattenLongIdent ~cut AtOffset ~jsx lid.txt
124
115
116
+ let rec ctxPathFromCoreType ~completionContext (coreType : Parsetree.core_type )
117
+ =
118
+ match coreType.ptyp_desc with
119
+ | Ptyp_constr ({txt = Lident "option" } , [innerTyp ]) ->
120
+ innerTyp
121
+ |> ctxPathFromCoreType ~completion Context
122
+ |> Option. map (fun innerTyp -> COption innerTyp)
123
+ | Ptyp_constr ({txt = Lident "array" } , [innerTyp ]) ->
124
+ Some (CArray (innerTyp |> ctxPathFromCoreType ~completion Context))
125
+ | Ptyp_constr (lid , _ ) ->
126
+ Some (CId (lid |> flattenLidCheckDot ~completion Context, Type ))
127
+ | _ -> None
128
+
129
+ let findCurrentlyLookingForInPattern ~completionContext
130
+ (pat : Parsetree.pattern ) =
131
+ match pat.ppat_desc with
132
+ | Ppat_constraint (_pat , typ ) -> (
133
+ match ctxPathFromCoreType ~completion Context typ with
134
+ | None -> None
135
+ | Some ctxPath -> Some (Type ctxPath))
136
+ | _ -> None
137
+
138
+ let mergeCurrentlyLookingFor (currentlyExpecting : currentlyExpecting option )
139
+ list =
140
+ match currentlyExpecting with
141
+ | None -> list
142
+ | Some currentlyExpecting -> currentlyExpecting :: list
143
+
144
+ let contextWithNewScope scope context = {context with scope}
145
+
125
146
(* * Scopes *)
126
147
let rec scopePattern ~scope (pat : Parsetree.pattern ) =
127
148
match pat.ppat_desc with
@@ -250,7 +271,9 @@ and completeValueBinding ~completionContext (vb : Parsetree.value_binding) :
250
271
= HasCursor
251
272
then (
252
273
print_endline " completing expression" ;
253
- let currentlyExpecting = findCurrentlyLookingForInPattern vb.pvb_pat in
274
+ let currentlyExpecting =
275
+ findCurrentlyLookingForInPattern ~completion Context vb.pvb_pat
276
+ in
254
277
completeExpr
255
278
~completion Context:
256
279
{
@@ -271,11 +294,7 @@ and completeExpr ~completionContext (expr : Parsetree.expression) :
271
294
~pos: completionContext.positionContext.beforeCursor
272
295
in
273
296
match expr.pexp_desc with
274
- | Pexp_ident lid ->
275
- (* An identifier, like `aaa` *)
276
- let lidPath = flattenLidCheckDot lid ~completion Context in
277
- if lid.loc |> locHasPos then Some (CId (lidPath, Value ), completionContext)
278
- else None
297
+ (* == VARIANTS == *)
279
298
| Pexp_construct (_id, Some {pexp_desc = Pexp_tuple args; pexp_loc})
280
299
when pexp_loc |> locHasPos ->
281
300
(* A constructor with multiple payloads, like: `Co(true, false)` or `Somepath.Co(false, true)` *)
@@ -302,56 +321,105 @@ and completeExpr ~completionContext (expr : Parsetree.expression) :
302
321
| Pexp_construct ({txt = Lident txt ; loc} , _ ) when loc |> locHasPos -> (
303
322
(* A constructor, like: `Co` *)
304
323
match completionContext.currentlyExpecting with
305
- | _ -> Some (CId ([txt], Module ), completionContext))
324
+ | _ ->
325
+ Some (CId ([txt], Module ) :: completionContext.ctxPath, completionContext)
326
+ )
306
327
| Pexp_construct (id , _ ) when id.loc |> locHasPos ->
307
328
(* A path, like: `Something.Co` *)
308
329
let lid = flattenLidCheckDot ~completion Context id in
309
- Some (CId (lid, Module ), completionContext)
330
+ Some (CId (lid, Module ) :: completionContext.ctxPath, completionContext)
331
+ (* == RECORDS == *)
332
+ | Pexp_ident {txt = Lident prefix} when Utils. hasBraces expr.pexp_attributes
333
+ ->
334
+ (* An ident with braces attribute corresponds to for example `{n}`.
335
+ Looks like a record but is parsed as an ident with braces. *)
336
+ let prefix = if prefix = " ()" then " " else prefix in
337
+ Some
338
+ ( CRecordField {prefix; seenFields = [] } :: completionContext.ctxPath,
339
+ completionContext (* TODO: This isn't correct *) )
310
340
| Pexp_record ([] , _ ) when expr.pexp_loc |> locHasPos ->
311
341
(* No fields means we're in a record body `{}` *)
312
342
Some
313
- ( CtxPath
314
- (CRecordField {prefix = " " ; seenFields = [] }
315
- :: completionContext.ctxPath),
343
+ ( CRecordField {prefix = " " ; seenFields = [] } :: completionContext.ctxPath,
316
344
completionContext (* TODO: This isn't correct *) )
317
- | Pexp_record (fields , _ ) when expr.pexp_loc |> locHasPos ->
345
+ | Pexp_record (fields , _ ) when expr.pexp_loc |> locHasPos -> (
318
346
(* A record with fields *)
319
347
let seenFields =
320
348
fields
321
349
|> List. map (fun (fieldName , _f ) -> Longident. last fieldName.Location. txt)
322
350
in
323
- fields
324
- |> Utils. findMap
325
- (fun
326
- ((fieldName , fieldExpr ) :
327
- Longident. t Location. loc * Parsetree. expression )
328
- ->
329
- (* Complete regular idents *)
330
- if locHasPos fieldName.loc then
331
- (* Cursor in field name, complete here *)
332
- match fieldName with
333
- | {txt = Lident prefix } ->
334
- Some
335
- ( CtxPath
336
- (CRecordField {prefix; seenFields}
337
- :: completionContext.ctxPath),
338
- completionContext (* TODO: This isn't correct *) )
339
- | fieldName ->
340
- Some
341
- ( CId (flattenLidCheckDot ~completion Context fieldName, Value ),
342
- completionContext )
343
- else if locHasPos fieldExpr.pexp_loc then
344
- completeExpr
345
- ~completion Context:
346
- {
347
- completionContext with
348
- ctxPath =
349
- CRecordField
350
- {prefix = fieldName.txt |> Longident. last; seenFields}
351
- :: completionContext.ctxPath;
352
- }
353
- fieldExpr
354
- else None )
351
+ let fieldToComplete =
352
+ fields
353
+ |> Utils. findMap
354
+ (fun
355
+ ((fieldName , fieldExpr ) :
356
+ Longident. t Location. loc * Parsetree. expression )
357
+ ->
358
+ (* Complete regular idents *)
359
+ if locHasPos fieldName.loc then
360
+ (* Cursor in field name, complete here *)
361
+ match fieldName with
362
+ | {txt = Lident prefix } ->
363
+ Some
364
+ ( CRecordField {prefix; seenFields}
365
+ :: completionContext.ctxPath,
366
+ completionContext (* TODO: This isn't correct *) )
367
+ | fieldName ->
368
+ Some
369
+ ( CId (flattenLidCheckDot ~completion Context fieldName, Value )
370
+ :: completionContext.ctxPath,
371
+ completionContext )
372
+ else if locHasPos fieldExpr.pexp_loc then
373
+ completeExpr
374
+ ~completion Context:
375
+ {
376
+ completionContext with
377
+ ctxPath =
378
+ CRecordField
379
+ {prefix = fieldName.txt |> Longident. last; seenFields}
380
+ :: completionContext.ctxPath;
381
+ }
382
+ fieldExpr
383
+ else None )
384
+ in
385
+ match fieldToComplete with
386
+ | None -> (
387
+ (* Check if there's a expr hole with an empty cursor for a field. This means completing for an empty field `{someField: <com>}`. *)
388
+ let fieldNameWithExprHole =
389
+ fields
390
+ |> Utils. findMap (fun (fieldName , fieldExpr ) ->
391
+ if
392
+ CompletionExpressions. isExprHole fieldExpr
393
+ && CursorPosition. classifyLoc fieldExpr.pexp_loc
394
+ ~pos: completionContext.positionContext.beforeCursor
395
+ = EmptyLoc
396
+ then Some (Longident. last fieldName.Location. txt)
397
+ else None )
398
+ in
399
+ (* We found no field to complete, but we know the cursor is inside this record body.
400
+ Check if the char to the left of the cursor is ',', if so, complete for record fields.*)
401
+ match
402
+ ( fieldNameWithExprHole,
403
+ completionContext.positionContext.charBeforeNoWhitespace )
404
+ with
405
+ | Some fieldName , _ ->
406
+ Some
407
+ ( CRecordField {prefix = fieldName; seenFields}
408
+ :: completionContext.ctxPath,
409
+ completionContext (* TODO: This isn't correct *) )
410
+ | None , Some ',' ->
411
+ Some
412
+ ( CRecordField {prefix = " " ; seenFields} :: completionContext.ctxPath,
413
+ completionContext (* TODO: This isn't correct *) )
414
+ | _ -> None )
415
+ | fieldToComplete -> fieldToComplete)
416
+ (* == IDENTS == *)
417
+ | Pexp_ident lid ->
418
+ (* An identifier, like `aaa` *)
419
+ let lidPath = flattenLidCheckDot lid ~completion Context in
420
+ if lid.loc |> locHasPos then
421
+ Some (CId (lidPath, Value ) :: completionContext.ctxPath, completionContext)
422
+ else None
355
423
| Pexp_match _ | Pexp_unreachable | Pexp_constant _
356
424
| Pexp_let (_, _, _)
357
425
| Pexp_function _
@@ -372,19 +440,18 @@ and completeExpr ~completionContext (expr : Parsetree.expression) :
372
440
| Pexp_constraint (_, _)
373
441
| Pexp_coerce (_, _, _)
374
442
| Pexp_send (_, _)
375
- | Pexp_new _
376
443
| Pexp_setinstvar (_, _)
377
444
| Pexp_override _
378
445
| Pexp_letmodule (_, _, _)
379
446
| Pexp_letexception (_, _)
380
447
| Pexp_assert _ | Pexp_lazy _
381
448
| Pexp_poly (_, _)
382
- | Pexp_object _
383
449
| Pexp_newtype (_, _)
384
450
| Pexp_pack _
385
451
| Pexp_open (_, _, _)
386
452
| Pexp_extension _ ->
387
453
None
454
+ | Pexp_object _ | Pexp_new _ -> (* These are irrelevant to ReScript *) None
388
455
389
456
let completion ~currentFile ~path ~debug ~offset ~posCursor text =
390
457
let positionContext = PositionContext. make ~offset ~pos Cursor text in
0 commit comments