Skip to content
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

complete pipe chains #656

Merged
merged 11 commits into from
Dec 21, 2022
125 changes: 69 additions & 56 deletions analysis/src/CompletionBackEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1282,7 +1282,7 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos
~env ~exact:true ~scope
|> completionsGetTypeEnv
with
| Some (typ, _envNotUsed) -> (
| Some (typ, envFromCompletionItem) -> (
let {
arrayModulePath;
optionModulePath;
Expand All @@ -1295,30 +1295,28 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos
} =
package.builtInCompletionModules
in
let getModulePath path =
let rec loop (path : Path.t) =
match path with
| Pident id -> [Ident.name id]
| Pdot (p, s, _) -> s :: loop p
| Papply _ -> []
in
let getBuiltinTypePath path =
match path with
| Path.Pident id when Ident.name id = "array" -> Some arrayModulePath
| Path.Pident id when Ident.name id = "option" -> Some optionModulePath
| Path.Pident id when Ident.name id = "string" -> Some stringModulePath
| Path.Pident id when Ident.name id = "int" -> Some intModulePath
| Path.Pident id when Ident.name id = "float" -> Some floatModulePath
| Path.Pident id when Ident.name id = "promise" ->
Some promiseModulePath
| Path.Pident id when Ident.name id = "list" -> Some listModulePath
| Path.Pident id when Ident.name id = "result" -> Some resultModulePath
| Path.Pident id when Ident.name id = "lazy_t" -> Some ["Lazy"]
| Path.Pident id when Ident.name id = "char" -> Some ["Char"]
| _ -> None
in
let rec expandPath (path : Path.t) =
match path with
| Path.Pident id when Ident.name id = "array" -> arrayModulePath
| Path.Pident id when Ident.name id = "option" -> optionModulePath
| Path.Pident id when Ident.name id = "string" -> stringModulePath
| Path.Pident id when Ident.name id = "int" -> intModulePath
| Path.Pident id when Ident.name id = "float" -> floatModulePath
| Path.Pident id when Ident.name id = "promise" -> promiseModulePath
| Path.Pident id when Ident.name id = "list" -> listModulePath
| Path.Pident id when Ident.name id = "result" -> resultModulePath
| Path.Pident id when Ident.name id = "lazy_t" -> ["Lazy"]
| Path.Pident id when Ident.name id = "char" -> ["Char"]
| _ -> (
match loop path with
| _ :: rest -> List.rev rest
| [] -> [])
| Pident id -> [Ident.name id]
| Pdot (p, s, _) -> s :: expandPath p
| Papply _ -> []
in
let getConstrPath typ =
let getTypePath typ =
match typ.Types.desc with
| Tconstr (path, _typeArgs, _)
| Tlink {desc = Tconstr (path, _typeArgs, _)}
Expand All @@ -1327,12 +1325,6 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos
Some path
| _ -> None
in
let fromType typ =
match getConstrPath typ with
| None -> None
| Some path -> Some (getModulePath path)
in
let lhsPath = fromType typ in
let rec removeRawOpen rawOpen modulePath =
match (rawOpen, modulePath) with
| [_], _ -> Some modulePath
Expand All @@ -1349,33 +1341,54 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos
| Some mp -> mp)
| [] -> modulePath
in
match lhsPath with
| Some modulePath -> (
match modulePath with
| _ :: _ ->
let modulePathMinusOpens =
modulePath
|> removeRawOpens package.opens
|> removeRawOpens rawOpens |> String.concat "."
in
let completionName name =
if modulePathMinusOpens = "" then name
else modulePathMinusOpens ^ "." ^ name
in
let completions =
modulePath @ [funNamePrefix]
|> getCompletionsForPath ~completionContext:Value ~exact:false
~package ~opens ~allFiles ~pos ~env ~scope
in
completions
|> List.map (fun (completion : Completion.t) ->
{
completion with
name = completionName completion.name;
env
(* Restore original env for the completion after x->foo()... *);
})
| [] -> [])
let completionPath =
match getTypePath typ with
| Some typePath -> (
match getBuiltinTypePath typePath with
| Some path -> Some path
| None -> (
match expandPath typePath with
| _ :: rest when rest <> [] ->
(* Assume a non-empty type path is coming from the compiler and
can be used as-is. *)
Some (List.rev rest)
| _ ->
(* Get the path from the comletion environment *)
let path = envFromCompletionItem.path in
if path = [] then None
else
let pathFromEnv =
if env.file.moduleName = envFromCompletionItem.file.moduleName
then path
else envFromCompletionItem.file.moduleName :: path
in
Some pathFromEnv))
| None -> None
in
match completionPath with
| Some completionPath ->
let completionPathMinusOpens =
completionPath
|> removeRawOpens package.opens
|> removeRawOpens rawOpens |> String.concat "."
in
let completionName name =
if completionPathMinusOpens = "" then name
else completionPathMinusOpens ^ "." ^ name
in
let completions =
completionPath @ [funNamePrefix]
|> getCompletionsForPath ~completionContext:Value ~exact:false
~package ~opens ~allFiles ~pos ~env ~scope
in
completions
|> List.map (fun (completion : Completion.t) ->
{
completion with
name = completionName completion.name;
env
(* Restore original env for the completion after x->foo()... *);
})
| None -> [])
| None -> [])

Expand Down
49 changes: 47 additions & 2 deletions analysis/src/CompletionFrontEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,46 @@ let rec exprToContextPath (e : Parsetree.expression) =
| Some contexPath -> Some (CPApply (contexPath, args |> List.map fst)))
| _ -> None

let completePipeChain ~(lhs : Parsetree.expression) =
(* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe,
so it can be completed.
Example:
someArray->Js.Array2.filter(v => v > 10)->Js.Array2.map(v => v + 2)->
will complete as:
Js.Array2.map(someArray->Js.Array2.filter(v => v > 10), v => v + 2)->
*)
match lhs.pexp_desc with
(* When the left side of the pipe we're completing is a function application.
Example: someArray->Js.Array2.map(v => v + 2)-> *)
| Pexp_apply
( {pexp_desc = Pexp_ident {txt = Lident "|."}},
[
(_, lhs);
(_, {pexp_desc = Pexp_apply (d, args); pexp_loc; pexp_attributes});
] ) ->
exprToContextPath
{
pexp_desc = Pexp_apply (d, (Nolabel, lhs) :: args);
pexp_loc;
pexp_attributes;
}
(* When the left side of the pipe we're completing is an identifier application.
Example: someArray->filterAllTheGoodStuff-> *)
| Pexp_apply
( {pexp_desc = Pexp_ident {txt = Lident "|."}},
[(_, lhs); (_, {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes})]
) ->
exprToContextPath
{
pexp_desc =
Pexp_apply
( {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes},
[(Nolabel, lhs)] );
pexp_loc;
pexp_attributes;
}
| _ -> None

let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
let offsetNoWhite = skipWhite text (offset - 1) in
let posNoWhite =
Expand Down Expand Up @@ -392,11 +432,16 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
(Loc.toString expr.pexp_loc)
in
let setPipeResult ~(lhs : Parsetree.expression) ~id =
match exprToContextPath lhs with
match completePipeChain ~lhs with
| None -> (
match exprToContextPath lhs with
| Some pipe ->
setResult (Cpath (CPPipe (pipe, id)));
true
| None -> false)
| Some pipe ->
setResult (Cpath (CPPipe (pipe, id)));
true
| None -> false
in
match expr.pexp_desc with
| Pexp_apply
Expand Down
Loading