diff --git a/CHANGELOG.md b/CHANGELOG.md index e54de7c53..6b2639d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/analysis/src/NewCompletions.ml b/analysis/src/NewCompletions.ml index cdfc9e542..b69a43af6 100644 --- a/analysis/src/NewCompletions.ml +++ b/analysis/src/NewCompletions.ml @@ -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 diff --git a/analysis/src/PartialParser.ml b/analysis/src/PartialParser.ml index 6b2035601..c4c0174e2 100644 --- a/analysis/src/PartialParser.ml +++ b/analysis/src/PartialParser.ml @@ -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 @@ -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 foo" *) let isLowercaseIdent id = @@ -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 = @@ -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 *) diff --git a/analysis/tests/src/Completion.res b/analysis/tests/src/Completion.res index 4d5d376e8..b8457f67b 100644 --- a/analysis/tests/src/Completion.res +++ b/analysis/tests/src/Completion.res @@ -67,4 +67,8 @@ let zzz = 11 let _ = Lib.foo(//~age, //^com ~ -~age=3, ~name="") \ No newline at end of file +~age=3, ~name="") + +let someObj = {"name": "a", "age": 32} + +//^com someObj["a diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index bb6439279..0737535ab 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -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}}} } ] @@ -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 + }] + diff --git a/server/src/server.ts b/server/src/server.ts index cbb461e0e..b407b640e 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -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 = {