diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c3e09ee..2ba76946c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ ## v1.10.0 +#### :rocket: New Feature + +- Add autocomplete for function argument values (booleans, variants and options. More values coming), both labelled and unlabelled. https://github.com/rescript-lang/rescript-vscode/pull/665 + #### :nail_care: Polish - Remove spacing between type definition in clients that do not support markdown links. https://github.com/rescript-lang/rescript-vscode/pull/619 diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index e1ace9947..22b0e25a0 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1455,6 +1455,123 @@ let getOpens ~debug ~rawOpens ~package ~env = (* Last open takes priority *) List.rev resolvedOpens +let getArgs ~env (t : Types.type_expr) ~full = + let rec getArgsLoop ~env (t : Types.type_expr) ~full ~currentArgumentPosition + = + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> + getArgsLoop ~full ~env ~currentArgumentPosition t1 + | Tarrow (Labelled l, tArg, tRet, _) -> + (SharedTypes.Completable.Labelled l, tArg) + :: getArgsLoop ~full ~env ~currentArgumentPosition tRet + | Tarrow (Optional l, tArg, tRet, _) -> + (Optional l, tArg) :: getArgsLoop ~full ~env ~currentArgumentPosition tRet + | Tarrow (Nolabel, tArg, tRet, _) -> + (Unlabelled {argumentPosition = currentArgumentPosition}, tArg) + :: getArgsLoop ~full ~env + ~currentArgumentPosition:(currentArgumentPosition + 1) + tRet + | Tconstr (path, typeArgs, _) -> ( + match References.digConstructor ~env ~package:full.package path with + | Some + ( env, + { + item = {decl = {type_manifest = Some t1; type_params = typeParams}}; + } ) -> + let t1 = t1 |> instantiateType ~typeParams ~typeArgs in + getArgsLoop ~full ~env ~currentArgumentPosition t1 + | _ -> []) + | _ -> [] + in + t |> getArgsLoop ~env ~full ~currentArgumentPosition:0 + +(** Pulls out a type we can complete from a type expr. *) +let rec extractType ~env ~package (t : Types.type_expr) = + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> extractType ~env ~package t1 + | Tconstr (Path.Pident {name = "option"}, [payloadTypeExpr], _) -> + Some (Completable.Toption (env, payloadTypeExpr)) + | Tconstr (Path.Pident {name = "bool"}, [], _) -> Some (Tbool env) + | Tconstr (path, _, _) -> ( + match References.digConstructor ~env ~package path with + | Some (env, {item = {decl = {type_manifest = Some t1}}}) -> + extractType ~env ~package t1 + | Some (env, {name; item = {decl; kind = Type.Variant constructors}}) -> + Some + (Tvariant + {env; constructors; variantName = name.txt; variantDecl = decl}) + | _ -> None) + | Ttuple expressions -> Some (Tuple (env, expressions)) + | _ -> None + +let filterItems items ~prefix = + if prefix = "" then items + else + items + |> List.filter (fun (item : Completion.t) -> + Utils.startsWith item.name prefix) + +let completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix + ~expandOption = + let namesUsed = Hashtbl.create 10 in + let rec completeTypedValueInner t ~env ~full ~prefix ~expandOption = + let items = + match t |> extractType ~env ~package:full.package with + | Some (Toption (env, typ)) when expandOption -> + typ |> completeTypedValueInner ~env ~full ~prefix ~expandOption:false + | Some (Tbool env) -> + [ + Completion.create ~name:"true" + ~kind:(Label (t |> Shared.typeToString)) + ~env; + Completion.create ~name:"false" + ~kind:(Label (t |> Shared.typeToString)) + ~env; + ] + |> filterItems ~prefix + | Some (Tvariant {env; constructors; variantDecl; variantName}) -> + constructors + |> List.map (fun (constructor : Constructor.t) -> + Completion.create + ~name: + (constructor.cname.txt + ^ + if constructor.args |> List.length > 0 then + "(" + ^ (constructor.args + |> List.map (fun _ -> "_") + |> String.concat ", ") + ^ ")" + else "") + ~kind: + (Constructor + ( constructor, + variantDecl |> Shared.declToString variantName )) + ~env) + |> filterItems ~prefix + | Some (Toption (env, t)) -> + [ + Completion.create ~name:"None" + ~kind:(Label (t |> Shared.typeToString)) + ~env; + Completion.create ~name:"Some(_)" + ~kind:(Label (t |> Shared.typeToString)) + ~env; + ] + |> filterItems ~prefix + | _ -> [] + in + (* Include all values and modules in completion if there's a prefix, not otherwise *) + if prefix = "" then items + else + items + @ completionForExportedValues ~env:envWhereCompletionStarted ~prefix + ~exact:false ~namesUsed + @ completionForExportedModules ~env:envWhereCompletionStarted ~prefix + ~exact:false ~namesUsed + in + completeTypedValueInner ~env ~full ~prefix ~expandOption + let processCompletable ~debug ~full ~scope ~env ~pos ~forHover (completable : Completable.t) = let package = full.package in @@ -1817,6 +1934,38 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i in (dec2, doc)) |> List.map mkDecorator + | Cargument {functionContextPath; argumentLabel; prefix} -> ( + let envWhereCompletionStarted = env in + let labels = + match + functionContextPath + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos + ~env ~exact:true ~scope + |> completionsGetTypeEnv + with + | Some (typ, _env) -> typ |> getArgs ~full ~env + | None -> [] + in + let targetLabel = + labels + |> List.find_opt (fun (label, _) -> + match argumentLabel with + | Unlabelled _ -> label = argumentLabel + | Labelled name | Optional name -> ( + match label with + | (Labelled n | Optional n) when name = n -> true + | _ -> false)) + in + match targetLabel with + | None -> [] + | Some (Optional _, typ) -> + typ + |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix + ~expandOption:true + | Some ((Unlabelled _ | Labelled _), typ) -> + typ + |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix + ~expandOption:false) | CnamedArg (cp, prefix, identsSeen) -> let labels = match @@ -1829,29 +1978,13 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i if debug then Printf.printf "Found type for function %s\n" (typ |> Shared.typeToString); - let rec getLabels ~env (t : Types.type_expr) = - match t.desc with - | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> getLabels ~env t1 - | Tarrow ((Labelled l | Optional l), tArg, tRet, _) -> - (l, tArg) :: getLabels ~env tRet - | Tarrow (Nolabel, _, tRet, _) -> getLabels ~env tRet - | Tconstr (path, typeArgs, _) -> ( - match References.digConstructor ~env ~package path with - | Some - ( env, - { - item = - { - decl = - {type_manifest = Some t1; type_params = typeParams}; - }; - } ) -> - let t1 = t1 |> instantiateType ~typeParams ~typeArgs in - getLabels ~env t1 - | _ -> []) - | _ -> [] - in - typ |> getLabels ~env + + typ |> getArgs ~full ~env + |> List.filter_map (fun arg -> + match arg with + | SharedTypes.Completable.Labelled name, a -> Some (name, a) + | Optional name, a -> Some (name, a) + | _ -> None) | None -> [] in let mkLabel (name, typ) = diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 953163b60..22b039103 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -106,8 +106,24 @@ let extractJsxProps ~(compName : Longident.t Location.loc) ~args = in args |> processProps ~acc:[] -let findNamedArgCompletable ~(args : arg list) ~endPos ~posBeforeCursor - ~(contextPath : Completable.contextPath) ~posAfterFunExpr = +let extractCompletableArgValueInfo exp = + match exp.Parsetree.pexp_desc with + | Pexp_ident {txt = Lident txt} -> Some txt + | Pexp_construct ({txt = Lident "()"}, _) -> Some "" + | Pexp_construct ({txt = Lident txt}, _) -> Some txt + | _ -> None + +let isExprHole exp = + match exp.Parsetree.pexp_desc with + | Pexp_extension ({txt = "rescript.exprhole"}, _) -> true + | _ -> false + +let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor + ~(contextPath : Completable.contextPath) ~posAfterFunExpr ~charBeforeCursor + ~isPipedExpr = + let fnHasCursor = + posAfterFunExpr <= posBeforeCursor && posBeforeCursor < endPos + in let allNames = List.fold_right (fun arg allLabels -> @@ -116,6 +132,7 @@ let findNamedArgCompletable ~(args : arg list) ~endPos ~posBeforeCursor | {label = None} -> allLabels) args [] in + let unlabelledCount = ref (if isPipedExpr then 1 else 0) in let rec loop args = match args with | {label = Some labelled; exp} :: rest -> @@ -123,17 +140,82 @@ let findNamedArgCompletable ~(args : arg list) ~endPos ~posBeforeCursor labelled.posStart <= posBeforeCursor && posBeforeCursor < labelled.posEnd then Some (Completable.CnamedArg (contextPath, labelled.name, allNames)) - else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then None + else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then + (* Completing in the assignment of labelled argument *) + match extractCompletableArgValueInfo exp with + | None -> None + | Some prefix -> + Some + (Cargument + { + functionContextPath = contextPath; + argumentLabel = Labelled labelled.name; + prefix; + }) + else if isExprHole exp then + Some + (Cargument + { + functionContextPath = contextPath; + argumentLabel = Labelled labelled.name; + prefix = ""; + }) else loop rest | {label = None; exp} :: rest -> - if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then None - else loop rest + if Res_parsetree_viewer.isTemplateLiteral exp then None + else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then + (* Completing in an unlabelled argument *) + match extractCompletableArgValueInfo exp with + | None -> None + | Some prefix -> + Some + (Cargument + { + functionContextPath = contextPath; + argumentLabel = + Unlabelled {argumentPosition = !unlabelledCount}; + prefix; + }) + else if isExprHole exp then + Some + (Cargument + { + functionContextPath = contextPath; + argumentLabel = Unlabelled {argumentPosition = !unlabelledCount}; + prefix = ""; + }) + else ( + unlabelledCount := !unlabelledCount + 1; + loop rest) | [] -> - if posAfterFunExpr <= posBeforeCursor && posBeforeCursor < endPos then - Some (CnamedArg (contextPath, "", allNames)) + if fnHasCursor then + match charBeforeCursor with + | Some '~' -> Some (Completable.CnamedArg (contextPath, "", allNames)) + | _ -> + Some + (Cargument + { + functionContextPath = contextPath; + argumentLabel = + Unlabelled {argumentPosition = !unlabelledCount}; + prefix = ""; + }) else None in - loop args + match args with + (* Special handling for empty fn calls, e.g. `let _ = someFn()` *) + | [ + {label = None; exp = {pexp_desc = Pexp_construct ({txt = Lident "()"}, _)}}; + ] + when fnHasCursor -> + Some + (Completable.Cargument + { + functionContextPath = contextPath; + argumentLabel = Unlabelled {argumentPosition = 0}; + prefix = ""; + }) + | _ -> loop args let rec exprToContextPath (e : Parsetree.expression) = match e.pexp_desc with @@ -553,15 +635,36 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = when Loc.end_ opLoc = posCursor -> (* Case foo-> *) setPipeResult ~lhs ~id:"" |> ignore - | Pexp_apply ({pexp_desc = Pexp_ident {txt = Lident "|."}}, [_; _]) -> - () - | Pexp_apply (funExpr, args) + | Pexp_apply + ( {pexp_desc = Pexp_ident {txt = Lident "|."}}, + [_; (_, {pexp_desc = Pexp_apply (funExpr, args)})] ) when (* Normally named arg completion fires when the cursor is right after the expression. E.g in foo(~<---there But it should not fire in foo(~a)<---there *) not (Loc.end_ expr.pexp_loc = posCursor && charBeforeCursor = Some ')') -> + (* Complete fn argument values and named args when the fn call is piped. E.g. someVar->someFn(). *) + let args = extractExpApplyArgs ~args in + let argCompletable = + match exprToContextPath funExpr with + | Some contextPath -> + findArgCompletables ~contextPath ~args + ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor + ~posAfterFunExpr:(Loc.end_ funExpr.pexp_loc) + ~charBeforeCursor ~isPipedExpr:true + | None -> None + in + + setResultOpt argCompletable + | Pexp_apply ({pexp_desc = Pexp_ident {txt = Lident "|."}}, [_; _]) -> + (* Ignore any other pipe. *) + () + | Pexp_apply (funExpr, args) + when not + (Loc.end_ expr.pexp_loc = posCursor + && charBeforeCursor = Some ')') -> + (* Complete fn argument values and named args when the fn call is _not_ piped. E.g. someFn(). *) let args = extractExpApplyArgs ~args in if debug then Printf.printf "Pexp_apply ...%s (%s)\n" @@ -578,16 +681,17 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = (Loc.toString exp.pexp_loc)) |> String.concat ", "); - let namedArgCompletable = + let argCompletable = match exprToContextPath funExpr with | Some contextPath -> - findNamedArgCompletable ~contextPath ~args + findArgCompletables ~contextPath ~args ~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor ~posAfterFunExpr:(Loc.end_ funExpr.pexp_loc) + ~charBeforeCursor ~isPipedExpr:false | None -> None in - setResultOpt namedArgCompletable + setResultOpt argCompletable | Pexp_send (lhs, {txt; loc}) -> ( (* e["txt"] If the string for txt is not closed, it could go over several lines. diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 358e25856..c90a93c4c 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -502,6 +502,11 @@ module Completable = struct (* Completion context *) type completionContext = Type | Value | Module | Field + type argumentLabel = + | Unlabelled of {argumentPosition: int} + | Labelled of string + | Optional of string + type contextPath = | CPString | CPArray @@ -526,6 +531,24 @@ module Completable = struct | Cpath of contextPath | Cjsx of string list * string * string list (** E.g. (["M", "Comp"], "id", ["id1", "id2"]) for ), complete for the value of `someBoolArg` (true or false). *) + + (** An extracted type from a type expr *) + type extractedType = + | Tuple of QueryEnv.t * Types.type_expr list + | Toption of QueryEnv.t * Types.type_expr + | Tbool of QueryEnv.t + | Tvariant of { + env: QueryEnv.t; + constructors: Constructor.t list; + variantDecl: Types.type_declaration; + variantName: string; + } let toString = let completionContextToString = function @@ -564,6 +587,16 @@ module Completable = struct | Cnone -> "Cnone" | Cjsx (sl1, s, sl2) -> "Cjsx(" ^ (sl1 |> list) ^ ", " ^ str s ^ ", " ^ (sl2 |> list) ^ ")" + | Cargument {functionContextPath; argumentLabel; prefix} -> + "Cargument " + ^ contextPathToString functionContextPath + ^ "(" + ^ (match argumentLabel with + | Unlabelled {argumentPosition} -> "$" ^ string_of_int argumentPosition + | Labelled name -> "~" ^ name + | Optional name -> "~" ^ name ^ "=?") + ^ (if prefix <> "" then "=" ^ prefix else "") + ^ ")" end module CursorPosition = struct diff --git a/analysis/tests/src/BrokenParserCases.res b/analysis/tests/src/BrokenParserCases.res new file mode 100644 index 000000000..877b7b0e4 --- /dev/null +++ b/analysis/tests/src/BrokenParserCases.res @@ -0,0 +1,5 @@ +// --- BROKEN PARSER CASES --- +// This below demonstrates an issue when what you're completing is the _last_ labelled argument, and there's a unit application after it. The parser wrongly merges the unit argument as the expression of the labelled argument assignment, where is should really let the trailing unit argument be, and set a %rescript.exprhole as the expression of the assignment, just like it normally does. +// let _ = someFn(~isOff=, ()) +// ^com + diff --git a/analysis/tests/src/CompletionFunctionArguments.res b/analysis/tests/src/CompletionFunctionArguments.res new file mode 100644 index 000000000..f002a444d --- /dev/null +++ b/analysis/tests/src/CompletionFunctionArguments.res @@ -0,0 +1,71 @@ +let someFn = (~isOn, ~isOff=false, ()) => { + if isOn && !isOff { + "on" + } else { + "off" + } +} + +let tLocalVar = false + +// let _ = someFn(~isOn=) +// ^com + +// let _ = someFn(~isOn=t) +// ^com + +// let _ = someFn(~isOff=) +// ^com + +let _ = someFn( + ~isOn={ + // switch someFn(~isOn=) + // ^com + true + }, +) + +let someOtherFn = (includeName, age, includeAge) => { + "Hello" ++ + (includeName ? " Some Name" : "") ++ + ", you are age " ++ + Belt.Int.toString(includeAge ? age : 0) +} + +// let _ = someOtherFn(f) +// ^com + +module OIncludeMeInCompletions = {} + +type someVariant = One | Two | Three(int, string) + +let someFnTakingVariant = ( + configOpt: option, + ~configOpt2=One, + ~config: someVariant, +) => { + ignore(config) + ignore(configOpt) + ignore(configOpt2) +} + +// let _ = someFnTakingVariant(~config=) +// ^com + +// let _ = someFnTakingVariant(~config=O) +// ^com + +// let _ = someFnTakingVariant(S) +// ^com + +// let _ = someFnTakingVariant(~configOpt2=O) +// ^com + +// let _ = someOtherFn() +// ^com + +// let _ = someOtherFn(1, 2, ) +// ^com + +// let _ = 1->someOtherFn(1, t) +// ^com diff --git a/analysis/tests/src/expected/BrokenParserCases.res.txt b/analysis/tests/src/expected/BrokenParserCases.res.txt new file mode 100644 index 000000000..7e4f89500 --- /dev/null +++ b/analysis/tests/src/expected/BrokenParserCases.res.txt @@ -0,0 +1,6 @@ +Complete src/BrokenParserCases.res 2:25 +posCursor:[2:25] posNoWhite:[2:24] Found expr:[2:11->2:30] +Pexp_apply ...[2:11->2:17] (~isOff2:19->2:24=...[2:27->2:29]) +Completable: Cargument Value[someFn]($0) +[] + diff --git a/analysis/tests/src/expected/CompletionFunctionArguments.res.txt b/analysis/tests/src/expected/CompletionFunctionArguments.res.txt new file mode 100644 index 000000000..07b8b0604 --- /dev/null +++ b/analysis/tests/src/expected/CompletionFunctionArguments.res.txt @@ -0,0 +1,213 @@ +Complete src/CompletionFunctionArguments.res 10:24 +posCursor:[10:24] posNoWhite:[10:23] Found expr:[10:11->10:25] +Pexp_apply ...[10:11->10:17] (~isOn10:19->10:23=...__ghost__[0:-1->0:-1]) +Completable: Cargument Value[someFn](~isOn) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 13:25 +posCursor:[13:25] posNoWhite:[13:24] Found expr:[13:11->13:26] +Pexp_apply ...[13:11->13:17] (~isOn13:19->13:23=...[13:24->13:25]) +Completable: Cargument Value[someFn](~isOn=t) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "tLocalVar", + "kind": 12, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 16:25 +posCursor:[16:25] posNoWhite:[16:24] Found expr:[16:11->16:26] +Pexp_apply ...[16:11->16:17] (~isOff16:19->16:24=...__ghost__[0:-1->0:-1]) +Completable: Cargument Value[someFn](~isOff) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 21:27 +posCursor:[21:27] posNoWhite:[21:26] Found expr:[19:8->25:1] +Pexp_apply ...[19:8->19:14] (~isOn20:3->20:7=...[21:7->23:8]) +posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:7->23:8] +posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:7->21:28] +posCursor:[21:27] posNoWhite:[21:26] Found expr:[21:14->21:28] +Pexp_apply ...[21:14->21:20] (~isOn21:22->21:26=...__ghost__[0:-1->0:-1]) +Completable: Cargument Value[someFn](~isOn) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 34:24 +posCursor:[34:24] posNoWhite:[34:23] Found expr:[34:11->34:25] +Pexp_apply ...[34:11->34:22] (...[34:23->34:24]) +Completable: Cargument Value[someOtherFn]($0=f) +[{ + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 51:39 +posCursor:[51:39] posNoWhite:[51:38] Found expr:[51:11->51:40] +Pexp_apply ...[51:11->51:30] (~config51:32->51:38=...__ghost__[0:-1->0:-1]) +Completable: Cargument Value[someFnTakingVariant](~config) +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null + }, { + "label": "Two", + "kind": 4, + "tags": [], + "detail": "Two\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null + }, { + "label": "Three(_, _)", + "kind": 4, + "tags": [], + "detail": "Three(int, string)\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 54:40 +posCursor:[54:40] posNoWhite:[54:39] Found expr:[54:11->54:41] +Pexp_apply ...[54:11->54:30] (~config54:32->54:38=...[54:39->54:40]) +Completable: Cargument Value[someFnTakingVariant](~config=O) +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null + }, { + "label": "OIncludeMeInCompletions", + "kind": 9, + "tags": [], + "detail": "module", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 57:32 +posCursor:[57:32] posNoWhite:[57:31] Found expr:[57:11->57:33] +Pexp_apply ...[57:11->57:30] (...[57:31->57:32]) +Completable: Cargument Value[someFnTakingVariant]($0=S) +[{ + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "someVariant", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 60:44 +posCursor:[60:44] posNoWhite:[60:43] Found expr:[60:11->60:45] +Pexp_apply ...[60:11->60:30] (~configOpt260:32->60:42=...[60:43->60:44]) +Completable: Cargument Value[someFnTakingVariant](~configOpt2=O) +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two | Three(int, string)", + "documentation": null + }, { + "label": "OIncludeMeInCompletions", + "kind": 9, + "tags": [], + "detail": "module", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 63:23 +posCursor:[63:23] posNoWhite:[63:22] Found expr:[63:11->63:24] +Pexp_apply ...[63:11->63:22] (...[63:23->63:24]) +Completable: Cargument Value[someOtherFn]($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 66:28 +posCursor:[66:28] posNoWhite:[66:27] Found expr:[66:11->66:30] +Pexp_apply ...[66:11->66:22] (...[66:23->66:24], ...[66:26->66:27]) +Completable: Cargument Value[someOtherFn]($2) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionFunctionArguments.res 69:30 +posCursor:[69:30] posNoWhite:[69:29] Found expr:[69:11->69:31] +Completable: Cargument Value[someOtherFn]($2=t) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "tLocalVar", + "kind": 12, + "tags": [], + "detail": "bool", + "documentation": null + }] + diff --git a/server/src/server.ts b/server/src/server.ts index 5b3be2505..e381851a2 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1111,7 +1111,7 @@ function onMessage(msg: p.Message) { codeActionProvider: true, renameProvider: { prepareProvider: true }, documentSymbolProvider: true, - completionProvider: { triggerCharacters: [".", ">", "@", "~", '"'] }, + completionProvider: { triggerCharacters: [".", ">", "@", "~", '"', "="] }, semanticTokensProvider: { legend: { tokenTypes: [