Skip to content

Commit ed392ac

Browse files
committed
Support autocomplete for objects from another module M.x[....
Fixes #360
1 parent bfe067e commit ed392ac

File tree

6 files changed

+97
-56
lines changed

6 files changed

+97
-56
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## master
22
- Fix issue where using paths of the form `./something` would show multiple copies of the same file in vscode.
33
- When hovering on a field access, show the instantiated type of the field.
4+
- Support autocomplete for objects from another module `M.x[...`.
45

56
## 1.2.1
67

analysis/src/NewCompletions.ml

+67-51
Original file line numberDiff line numberDiff line change
@@ -672,15 +672,19 @@ let localValueCompletions ~pos ~(env : QueryEnv.t) suffix =
672672
{(emptyDeclared c.cname.txt) with item = Constructor (c, t)}))
673673
else results
674674
in
675-
if suffix = "" || not (isCapitalized suffix) then
676-
results
677-
@ completionForDeclareds ~pos env.file.stamps.values suffix (fun v ->
678-
Value v)
679-
@ completionForDeclareds ~pos env.file.stamps.types suffix (fun t -> Type t)
680-
@ (completionForFields env.exported.types env.file.stamps.types suffix
681-
|> List.map (fun (f, t) ->
682-
{(emptyDeclared f.fname.txt) with item = Field (f, t)}))
683-
else results
675+
let results =
676+
if suffix = "" || not (isCapitalized suffix) then
677+
results
678+
@ completionForDeclareds ~pos env.file.stamps.values suffix (fun v ->
679+
Value v)
680+
@ completionForDeclareds ~pos env.file.stamps.types suffix (fun t ->
681+
Type t)
682+
@ (completionForFields env.exported.types env.file.stamps.types suffix
683+
|> List.map (fun (f, t) ->
684+
{(emptyDeclared f.fname.txt) with item = Field (f, t)}))
685+
else results
686+
in
687+
results |> List.map (fun r -> (r, env))
684688

685689
let valueCompletions ~(env : QueryEnv.t) suffix =
686690
Log.log (" - Completing in " ^ Uri2.toString env.file.uri);
@@ -705,17 +709,20 @@ let valueCompletions ~(env : QueryEnv.t) suffix =
705709
{(emptyDeclared c.cname.txt) with item = Constructor (c, t)})))
706710
else results
707711
in
708-
if suffix = "" || not (isCapitalized suffix) then (
709-
Log.log " -- not capitalized";
710-
results
711-
@ completionForExporteds env.exported.values env.file.stamps.values suffix
712-
(fun v -> Value v)
713-
@ completionForExporteds env.exported.types env.file.stamps.types suffix
714-
(fun t -> Type t)
715-
@ (completionForFields env.exported.types env.file.stamps.types suffix
716-
|> List.map (fun (f, t) ->
717-
{(emptyDeclared f.fname.txt) with item = Field (f, t)})))
718-
else results
712+
let results =
713+
if suffix = "" || not (isCapitalized suffix) then (
714+
Log.log " -- not capitalized";
715+
results
716+
@ completionForExporteds env.exported.values env.file.stamps.values suffix
717+
(fun v -> Value v)
718+
@ completionForExporteds env.exported.types env.file.stamps.types suffix
719+
(fun t -> Type t)
720+
@ (completionForFields env.exported.types env.file.stamps.types suffix
721+
|> List.map (fun (f, t) ->
722+
{(emptyDeclared f.fname.txt) with item = Field (f, t)})))
723+
else results
724+
in
725+
results |> List.map (fun r -> (r, env))
719726

720727
let attributeCompletions ~(env : QueryEnv.t) ~suffix =
721728
let results = [] in
@@ -726,15 +733,18 @@ let attributeCompletions ~(env : QueryEnv.t) ~suffix =
726733
suffix (fun m -> Module m)
727734
else results
728735
in
729-
if suffix = "" || not (isCapitalized suffix) then
730-
results
731-
@ completionForExporteds env.exported.values env.file.stamps.values suffix
732-
(fun v -> Value v)
733-
(* completionForExporteds(env.exported.types, env.file.stamps.types, suffix, t => Type(t)) @ *)
734-
@ (completionForFields env.exported.types env.file.stamps.types suffix
735-
|> List.map (fun (f, t) ->
736-
{(emptyDeclared f.fname.txt) with item = Field (f, t)}))
737-
else results
736+
let results =
737+
if suffix = "" || not (isCapitalized suffix) then
738+
results
739+
@ completionForExporteds env.exported.values env.file.stamps.values suffix
740+
(fun v -> Value v)
741+
(* completionForExporteds(env.exported.types, env.file.stamps.types, suffix, t => Type(t)) @ *)
742+
@ (completionForFields env.exported.types env.file.stamps.types suffix
743+
|> List.map (fun (f, t) ->
744+
{(emptyDeclared f.fname.txt) with item = Field (f, t)}))
745+
else results
746+
in
747+
results |> List.map (fun r -> (r, env))
738748

739749
(* TODO filter out things that are defined after the current position *)
740750
let resolveRawOpens ~env ~rawOpens ~package =
@@ -804,7 +814,7 @@ let getItems ~full ~rawOpens ~allFiles ~pos ~dotpath =
804814
(fun results env ->
805815
let completionsFromThisOpen = valueCompletions ~env suffix in
806816
List.filter
807-
(fun declared ->
817+
(fun (declared, _env) ->
808818
if Hashtbl.mem alreadyUsedIdentifiers declared.name.txt then
809819
false
810820
else (
@@ -819,7 +829,7 @@ let getItems ~full ~rawOpens ~allFiles ~pos ~dotpath =
819829
allFiles |> FileSet.elements
820830
|> Utils.filterMap (fun name ->
821831
if Utils.startsWith name suffix && not (String.contains name '-')
822-
then Some {(emptyDeclared name) with item = FileModule name}
832+
then Some ({(emptyDeclared name) with item = FileModule name}, env)
823833
else None)
824834
in
825835
locallyDefinedValues @ valuesFromOpens @ localModuleNames
@@ -863,15 +873,16 @@ let getItems ~full ~rawOpens ~allFiles ~pos ~dotpath =
863873
(Some (env, fields, typ))
864874
with
865875
| None -> []
866-
| Some (_env, fields, typ) ->
876+
| Some (env, fields, typ) ->
867877
fields
868878
|> Utils.filterMap (fun field ->
869879
if Utils.startsWith field.fname.txt lastField then
870880
Some
871-
{
872-
(emptyDeclared field.fname.txt) with
873-
item = Field (field, typ);
874-
}
881+
( {
882+
(emptyDeclared field.fname.txt) with
883+
item = Field (field, typ);
884+
},
885+
env )
875886
else None))))
876887
| None -> [])
877888
| QualifiedRecordAccess path -> (
@@ -924,7 +935,7 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens
924935
let declareds = processDotPath ~exact:true (componentPath @ ["make"]) in
925936
let labels =
926937
match declareds with
927-
| {SharedTypes.item = Value typ} :: _ ->
938+
| ({SharedTypes.item = Value typ}, _env) :: _ ->
928939
let rec getFields (texp : Types.type_expr) =
929940
match texp.desc with
930941
| Tfield (name, _, t1, t2) ->
@@ -984,7 +995,9 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens
984995
(* TODO(#107): figure out why we're getting duplicates. *)
985996
declareds |> Utils.dedup
986997
|> List.map
987-
(fun {SharedTypes.name = {txt = name}; deprecated; docstring; item} ->
998+
(fun
999+
({SharedTypes.name = {txt = name}; deprecated; docstring; item}, _env)
1000+
->
9881001
mkItem ~name ~kind:(kindToInt item) ~deprecated
9891002
~detail:(detail name item) ~docstring)
9901003
| Cpipe (pipe, partialName) -> (
@@ -1044,8 +1057,7 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens
10441057
match String.split_on_char '.' pipeId with
10451058
| x :: fieldNames -> (
10461059
match [x] |> processDotPath ~exact:true with
1047-
| {SharedTypes.item = Value typ} :: _ -> (
1048-
let env = QueryEnv.fromFile full.file in
1060+
| ({SharedTypes.item = Value typ}, env) :: _ -> (
10491061
match getFields ~env ~typ fieldNames with
10501062
| None -> None
10511063
| Some (typ1, _env1) -> fromType typ1)
@@ -1096,10 +1108,12 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens
10961108
let dotpath = modulePath @ [partialName] in
10971109
let declareds = dotpath |> processDotPath ~exact:false in
10981110
declareds
1099-
|> List.filter (fun {item} ->
1111+
|> List.filter (fun ({item}, _env) ->
11001112
match item with Value _ -> true | _ -> false)
11011113
|> List.map
1102-
(fun {SharedTypes.name = {txt = name}; deprecated; docstring; item}
1114+
(fun
1115+
( {SharedTypes.name = {txt = name}; deprecated; docstring; item},
1116+
_env )
11031117
->
11041118
mkItem ~name:(completionName name) ~kind:(kindToInt item)
11051119
~detail:(detail name item) ~deprecated ~docstring)
@@ -1152,7 +1166,7 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens
11521166
| Clabel (funPath, prefix, identsSeen) ->
11531167
let labels =
11541168
match funPath |> processDotPath ~exact:true with
1155-
| {SharedTypes.item = Value typ} :: _ ->
1169+
| ({SharedTypes.item = Value typ}, _env) :: _ ->
11561170
let rec getLabels (t : Types.type_expr) =
11571171
match t.desc with
11581172
| Tlink t1 | Tsubst t1 -> getLabels t1
@@ -1200,8 +1214,8 @@ let processCompletable ~processDotPath ~full ~package ~rawOpens
12001214
in
12011215
let env0 = QueryEnv.fromFile full.file in
12021216
let env, fields =
1203-
match [lhs] |> processDotPath ~exact:true with
1204-
| {SharedTypes.item = Value typ} :: _ -> getObjectFields ~env:env0 typ
1217+
match lhs |> processDotPath ~exact:true with
1218+
| ({SharedTypes.item = Value typ}, env) :: _ -> getObjectFields ~env typ
12051219
| _ -> (env0, [])
12061220
in
12071221
let labels = resolvePath ~env fields path in
@@ -1225,7 +1239,9 @@ let getCompletable ~textOpt ~pos =
12251239
match PartialParser.findCompletable text offset with
12261240
| None -> None
12271241
| Some completable ->
1228-
let rawOpens = PartialParser.findOpens text offset in
1242+
let offsetFromLineStart = offset - snd pos in
1243+
(* try to avoid confusion e.g. unclosed quotes at current position *)
1244+
let rawOpens = PartialParser.findOpens text offsetFromLineStart in
12291245
Some (completable, rawOpens)))
12301246

12311247
let computeCompletions ~completable ~full ~pos ~rawOpens =
@@ -1239,17 +1255,17 @@ let computeCompletions ~completable ~full ~pos ~rawOpens =
12391255
Take the last position before pos if any, or just return the first element. *)
12401256
let rec prioritize decls =
12411257
match decls with
1242-
| d1 :: d2 :: rest ->
1258+
| (d1, e1) :: (d2, e2) :: rest ->
12431259
let pos2 = d2.extentLoc.loc_start |> Utils.tupleOfLexing in
1244-
if pos2 >= pos then prioritize (d1 :: rest)
1260+
if pos2 >= pos then prioritize ((d1, e1) :: rest)
12451261
else
12461262
let pos1 = d1.extentLoc.loc_start |> Utils.tupleOfLexing in
1247-
if pos1 <= pos2 then prioritize (d2 :: rest)
1248-
else prioritize (d1 :: rest)
1263+
if pos1 <= pos2 then prioritize ((d2, e2) :: rest)
1264+
else prioritize ((d1, e1) :: rest)
12491265
| [] | [_] -> decls
12501266
in
12511267
declareds
1252-
|> List.filter (fun {SharedTypes.name = {txt}} -> txt = last)
1268+
|> List.filter (fun ({SharedTypes.name = {txt}}, _env) -> txt = last)
12531269
|> prioritize
12541270
| _ -> declareds
12551271
in

analysis/src/PartialParser.ml

+10-5
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ type completable =
190190
| Cdotpath of string list (** e.g. ["M", "foo"] for M.foo *)
191191
| Cjsx of string list * string * string list
192192
(** E.g. (["M", "Comp"], "id", ["id1", "id2"]) for <M.Comp id1=... id2=... ... id *)
193-
| Cobj of string * string list * string
194-
(** e.g. ("foo", ["a", "b"], "bar") for foo["a"]["b"]["bar" *)
193+
| Cobj of string list * string list * string
194+
(** e.g. (["M", "foo"], ["a", "b"], "bar") for M.foo["a"]["b"]["bar" *)
195195
| Cpipe of pipe * string (** E.g. ("x", "foo") for "x->foo" *)
196196

197197
let isLowercaseIdent id =
@@ -238,18 +238,23 @@ let findCompletable text offset =
238238
let mkObj ~off ~partialName =
239239
let off = skipWhite text off in
240240
let rec loop off path i =
241-
if i < 0 then Some ([], String.sub text 0 (i - 1))
241+
if i < 0 then
242+
let id = String.sub text 0 (i - 1) in
243+
Some ([], [id])
242244
else
243245
match text.[i] with
244-
| 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' -> loop off path (i - 1)
246+
| 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '.' ->
247+
loop off path (i - 1)
245248
| ']' when i > 1 && text.[i - 1] = '"' ->
246249
let i0 = i - 2 in
247250
let i1 = startOfLident text i0 in
248251
let ident = String.sub text i1 (i0 - i1 + 1) in
249252
if ident <> "" && i1 > 1 && text.[i1 - 1] = '"' && text.[i1 - 2] = '['
250253
then loop (off - i + i1 - 3) (ident :: path) (i1 - 3)
251254
else None
252-
| _ -> Some (path, String.sub text (i + 1) (off - i))
255+
| _ ->
256+
let id = String.sub text (i + 1) (off - i) in
257+
Some (path, Str.split (Str.regexp_string ".") id)
253258
in
254259
match loop off [] off with
255260
| None -> None

analysis/tests/src/Completion.res

+2
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,5 @@ let make = () => {
9898
// ^com my
9999
<> </>
100100
}
101+
102+
// ^com Obj.object["

analysis/tests/src/Obj.res

+2
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ module Rec = {
77

88
let recordVal: recordt = assert false
99
}
10+
11+
let object: objT = {"name": "abc", "age": 4}

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

+15
Original file line numberDiff line numberDiff line change
@@ -729,3 +729,18 @@ Complete tests/src/Completion.res 96:3
729729
"documentation": null
730730
}]
731731

732+
Complete tests/src/Completion.res 100:3
733+
[{
734+
"label": "name",
735+
"kind": 4,
736+
"tags": [],
737+
"detail": "string",
738+
"documentation": null
739+
}, {
740+
"label": "age",
741+
"kind": 4,
742+
"tags": [],
743+
"detail": "int",
744+
"documentation": null
745+
}]
746+

0 commit comments

Comments
 (0)