Skip to content

Add autocompletion for object access of the form foo["bar"] #315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
- Fix issue in JSX autocomplete when the component is declared external.
- Fix jump-to-definition for uncurried calls.
- Fix issue where values for autocomplete were pulled from implementations instead of interfaces.
- Add autocompletion for object access of the form foo["bar"].

## 1.1.3

Expand Down
31 changes: 31 additions & 0 deletions analysis/src/NewCompletions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,37 @@ let processCompletable ~findItems ~full ~package ~rawOpens
|> List.filter (fun (name, _t) ->
Utils.startsWith name prefix && not (List.mem name identsSeen))
|> List.map mkLabel
| Cobj (lhs, prefix) ->
let labels =
match [lhs] |> findItems ~exact:true with
| {SharedTypes.item = Value typ} :: _ ->
let rec getFields (texp : Types.type_expr) =
match texp.desc with
| Tfield (name, _, t1, t2) ->
let fields = t2 |> getFields in
(name, t1) :: fields
| Tlink te -> te |> getFields
| Tvar None -> []
| _ -> []
in
let rec getObj (t : Types.type_expr) =
match t.desc with
| Tlink t1 | Tsubst t1 -> getObj t1
| Tobject (tObj, _) -> getFields tObj
| _ -> []
in
getObj typ
| _ -> []
in
let mkLabel_ name typString =
mkItem ~name ~kind:4 ~deprecated:None ~detail:typString ~docstring:[]
in
let mkLabel (name, typ) = mkLabel_ name (typ |> Shared.typeToString) in
if labels = [] then []
else
labels
|> List.filter (fun (name, _t) -> Utils.startsWith name prefix)
|> List.map mkLabel

let getCompletable ~textOpt ~pos =
match textOpt with
Expand Down
19 changes: 18 additions & 1 deletion analysis/src/PartialParser.ml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ let skipOptVariantExtension text i =
arg ::= id | id = [?] atomicExpr
atomicExpr ::= id | "abc" | 'a' | 42 | `...` | optVariant {...} | optVariant (...) | <...> | [...]
optVariant ::= a | A | #a | #A | _nothing_
*)
*)
let findJsxContext text offset =
let rec loop identsSeen i =
let i = skipWhite text i in
Expand Down Expand Up @@ -187,6 +187,7 @@ type completable =
| Cpath of string list (** e.g. ["M", "foo"] for M.foo *)
| Cjsx of string list * string * string list
(** E.g. (["M", "Comp"], "id", ["id1", "id2"]) for <M.Comp id1=... id2=... ... id *)
| Cobj of string * string (** e.g. ("foo", "bar") for foo['bar *)
| Cpipe of pipe * string (** E.g. ("x", "foo") for "x->foo" *)

let isLowercaseIdent id =
Expand Down Expand Up @@ -228,6 +229,19 @@ let findCompletable text offset =
| None -> None
| Some lhs -> Some (Cpipe (lhs, partialName))
in
let mkObj ~off ~partialName =
let off = skipWhite text off in
let rec loop i =
if i < 0 then Some (String.sub text 0 (i - 1))
else
match text.[i] with
| 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' -> loop (i - 1)
| _ -> Some (String.sub text (i + 1) (off - i))
in
match loop off with
| None -> None
| Some lhs -> Some (Cobj (lhs, partialName))
in

let suffix i = String.sub text (i + 1) (offset - (i + 1)) in
let rec loop i =
Expand All @@ -243,6 +257,9 @@ let findCompletable text offset =
let funPath, identsSeen = findCallFromArgument text (i - 1) in
Some (Clabel (funPath, labelPrefix, identsSeen))
| '@' -> Some (Cdecorator (suffix i))
| '"' when i > 0 && text.[i - 1] = '[' ->
let partialName = suffix i in
mkObj ~off:(i - 2) ~partialName
| 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_' -> loop (i - 1)
| ' ' when i = offset - 1 -> (
(* autocomplete with no id: check if inside JSX *)
Expand Down
6 changes: 5 additions & 1 deletion analysis/tests/src/Completion.res
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,8 @@ let zzz = 11

let _ = Lib.foo(//~age,
//^com ~
~age=3, ~name="")
~age=3, ~name="")

let someObj = {"name": "a", "age": 32}

//^com someObj["a
14 changes: 14 additions & 0 deletions analysis/tests/src/expected/Completion.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,11 @@ DocumentSymbol tests/src/Completion.res
"name": "zzz",
"kind": 13,
"location": {"uri": "Completion.res", "range": {"start": {"line": 49, "character": 4}, "end": {"line": 49, "character": 7}}}
},
{
"name": "someObj",
"kind": 19,
"location": {"uri": "Completion.res", "range": {"start": {"line": 71, "character": 4}, "end": {"line": 71, "character": 11}}}
}
]

Expand Down Expand Up @@ -598,3 +603,12 @@ Complete tests/src/Completion.res 67:2
"documentation": null
}]

Complete tests/src/Completion.res 72:2
[{
"label": "age",
"kind": 4,
"tags": [],
"detail": "int",
"documentation": null
}]

2 changes: 1 addition & 1 deletion server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ function onMessage(msg: m.Message) {
renameProvider: { prepareProvider: true },
// disabled right now until we use the parser to show non-stale symbols per keystroke
// documentSymbolProvider: true,
completionProvider: { triggerCharacters: [".", ">", "@", "~"] },
completionProvider: { triggerCharacters: [".", ">", "@", "~", '"'] },
},
};
let response: m.ResponseMessage = {
Expand Down