From 618b0b13ed4a21d151fe77c93e9791cb9fbbd308 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Tue, 26 Jan 2021 13:45:56 +0100 Subject: [PATCH 1/5] Explore autocomplete for pipe-first. --- src/rescript-editor-support/NewCompletions.re | 119 +++++++++++------- src/rescript-editor-support/PartialParser.re | 19 ++- .../RescriptEditorSupport.re | 5 +- 3 files changed, 94 insertions(+), 49 deletions(-) diff --git a/src/rescript-editor-support/NewCompletions.re b/src/rescript-editor-support/NewCompletions.re index f4da1636..9e65d40f 100644 --- a/src/rescript-editor-support/NewCompletions.re +++ b/src/rescript-editor-support/NewCompletions.re @@ -574,6 +574,30 @@ let getItems = module J = JsonShort; +let mkItem = (~name, ~kind, ~detail, ~docstring, ~uri, ~pos_lnum) => { + J.o([ + ("label", J.s(name)), + ("kind", J.i(kind)), + ("detail", detail |> J.s), + ( + "documentation", + J.o([ + ("kind", J.s("markdown")), + ( + "value", + J.s( + (docstring |? "No docs") + ++ "\n\n" + ++ uri + ++ ":" + ++ string_of_int(pos_lnum), + ), + ), + ]), + ), + ]); +}; + let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => { let parameters = switch (maybeText) { @@ -582,19 +606,14 @@ let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => { switch (PartialParser.positionToOffset(text, pos)) { | None => None | Some(offset) => - switch (PartialParser.findCompletable(text, offset)) { - | None => None - | Some(Clabel(_)) => - /* Not supported yet */ - None - | Some(Cpath(parts)) => Some((text, offset, parts)) - } + Some((text, offset, PartialParser.findCompletable(text, offset))) } }; let items = switch (parameters) { | None => [] - | Some((text, offset, parts)) => + + | Some((text, offset, Some(Cpath(parts)))) => let rawOpens = PartialParser.findOpens(text, offset); let allModules = package.TopTypes.localModules @ package.dependencyModules; @@ -609,45 +628,57 @@ let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => { ~parts, ); /* TODO(#107): figure out why we're getting duplicates. */ - Utils.dedup(items); + items + |> Utils.dedup + |> List.map( + ( + ( + uri, + { + SharedTypes.name: {txt: name, loc: {loc_start: {pos_lnum}}}, + docstring, + item, + }, + ), + ) => + mkItem( + ~name, + ~kind=kindToInt(item), + ~detail=detail(name, item), + ~docstring, + ~uri, + ~pos_lnum, + ) + ); + + | Some((_text, _offset, Some(Cpipe(s)))) => [ + mkItem( + ~name="Foo.pipe", + ~kind=9, + ~detail=s, + ~docstring=None, + ~uri="uri", + ~pos_lnum=0, + ), + mkItem( + ~name="Foo.pine", + ~kind=9, + ~detail=s, + ~docstring=None, + ~uri="uri", + ~pos_lnum=0, + ), + ] + + | Some((_, _, Some(Clabel(_)))) => + // not supported yet + [] + + | Some((_, _, None)) => [] }; if (items == []) { J.null; } else { - items - |> List.map( - ( - ( - uri, - { - SharedTypes.name: {txt: name, loc: {loc_start: {pos_lnum}}}, - docstring, - item, - }, - ), - ) => { - J.o([ - ("label", J.s(name)), - ("kind", J.i(kindToInt(item))), - ("detail", detail(name, item) |> J.s), - ( - "documentation", - J.o([ - ("kind", J.s("markdown")), - ( - "value", - J.s( - (docstring |? "No docs") - ++ "\n\n" - ++ Uri2.toString(uri) - ++ ":" - ++ string_of_int(pos_lnum), - ), - ), - ]), - ), - ]) - }) - |> J.l; + items |> J.l; }; }; diff --git a/src/rescript-editor-support/PartialParser.re b/src/rescript-editor-support/PartialParser.re index 097320fc..da9c0c80 100644 --- a/src/rescript-editor-support/PartialParser.re +++ b/src/rescript-editor-support/PartialParser.re @@ -66,13 +66,23 @@ let rec startOfLident = (text, i) => type completable = | Clabel(string) - | Cpath(list(string)); + | Cpath(list(string)) + | Cpipe(string); let findCompletable = (text, offset) => { let mkPath = s => { - let parts = Str.split(Str.regexp_string("."), s); - let parts = s.[String.length(s) - 1] == '.' ? parts @ [""] : parts; - Cpath(parts); + let len = String.length(s); + let pipeParts = Str.split(Str.regexp_string("->"), s); + if (len > 1 + && s.[len - 2] == '-' + && s.[len - 1] == '>' + || List.length(pipeParts) > 1) { + Cpipe(s); + } else { + let parts = Str.split(Str.regexp_string("."), s); + let parts = s.[len - 1] == '.' ? parts @ [""] : parts; + Cpath(parts); + }; }; let rec loop = i => { @@ -80,6 +90,7 @@ let findCompletable = (text, offset) => { ? Some(mkPath(String.sub(text, i + 1, offset - (i + 1)))) : ( switch (text.[i]) { + | '>' when i > 0 && text.[i - 1] == '-' => loop(i - 2) | '~' => Some(Clabel(String.sub(text, i + 1, offset - (i + 1)))) | 'a'..'z' | 'A'..'Z' diff --git a/src/rescript-editor-support/RescriptEditorSupport.re b/src/rescript-editor-support/RescriptEditorSupport.re index c6c3e451..a52b4e63 100644 --- a/src/rescript-editor-support/RescriptEditorSupport.re +++ b/src/rescript-editor-support/RescriptEditorSupport.re @@ -5,7 +5,10 @@ let capabilities = J.o([ ("textDocumentSync", J.i(1)), ("hoverProvider", J.t), - ("completionProvider", J.o([("triggerCharacters", J.l([J.s(".")]))])), + ( + "completionProvider", + J.o([("triggerCharacters", J.l([J.s("."), J.s(">")]))]), + ), ("definitionProvider", J.t), ("typeDefinitionProvider", J.t), ("referencesProvider", J.t), From 6d9b71a2451b0a66e936a7c4201c6d99a7dafe47 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 29 Jan 2021 12:50:45 +0100 Subject: [PATCH 2/5] Clean up query env creation. --- src/rescript-editor-support/Hover.re | 4 ++-- src/rescript-editor-support/ProcessExtra.re | 5 +---- src/rescript-editor-support/Query.re | 16 +++------------- src/rescript-editor-support/References.re | 12 ++++++------ 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/rescript-editor-support/Hover.re b/src/rescript-editor-support/Hover.re index fd1f0ca6..c7faa81f 100644 --- a/src/rescript-editor-support/Hover.re +++ b/src/rescript-editor-support/Hover.re @@ -78,7 +78,7 @@ let newHover = (~rootUri, ~file: SharedTypes.file, ~getModule, loc) => { showModule(~docstring, ~name, ~file, declared); | LModule(GlobalReference(moduleName, path, tip)) => let%opt file = getModule(moduleName); - let env = {Query.file, exported: file.contents.exported}; + let env = Query.fileEnv(file); let%opt (env, name) = Query.resolvePath(~env, ~path, ~getModule); let%opt stamp = Query.exportedForTip(~env, name, tip); let%opt md = Hashtbl.find_opt(file.stamps.modules, stamp); @@ -116,7 +116,7 @@ let newHover = (~rootUri, ~file: SharedTypes.file, ~getModule, loc) => { let fromType = (~docstring, typ) => { let typeString = codeBlock(typ |> Shared.typeToString); let extraTypeInfo = { - let env = {Query.file, exported: file.contents.exported}; + let env = Query.fileEnv(file); let%opt path = typ |> Shared.digConstructor; let%opt (_env, {docstring, name: {txt}, item: {decl}}) = digConstructor(~env, ~getModule, path); diff --git a/src/rescript-editor-support/ProcessExtra.re b/src/rescript-editor-support/ProcessExtra.re index 49b9c72e..cf24a627 100644 --- a/src/rescript-editor-support/ProcessExtra.re +++ b/src/rescript-editor-support/ProcessExtra.re @@ -123,10 +123,7 @@ module F = ], ); }; - let env = { - Query.file: Collector.file, - exported: Collector.file.contents.exported, - }; + let env = Query.fileEnv(Collector.file); let getTypeAtPath = getTypeAtPath(~env); diff --git a/src/rescript-editor-support/Query.re b/src/rescript-editor-support/Query.re index fafa3951..3a84d043 100644 --- a/src/rescript-editor-support/Query.re +++ b/src/rescript-editor-support/Query.re @@ -1,7 +1,5 @@ open SharedTypes; -/* TODO maybe keep track of the "current module path" */ -/* maybe add a "current module path" for debugging purposes */ type queryEnv = { file, exported, @@ -111,11 +109,7 @@ let rec resolvePath = (~env, ~path, ~getModule) => { | `Local(env, name) => Some((env, name)) | `Global(moduleName, fullPath) => let%opt file = getModule(moduleName); - resolvePath( - ~env={file, exported: file.contents.exported}, - ~path=fullPath, - ~getModule, - ); + resolvePath(~env=fileEnv(file), ~path=fullPath, ~getModule); }; }; @@ -131,11 +125,7 @@ let resolveFromStamps = (~env, ~path, ~getModule, ~pos) => { | `Local(env, name) => Some((env, name)) | `Global(moduleName, fullPath) => let%opt file = getModule(moduleName); - resolvePath( - ~env={file, exported: file.contents.exported}, - ~path=fullPath, - ~getModule, - ); + resolvePath(~env=fileEnv(file), ~path=fullPath, ~getModule); }; }; }; @@ -192,7 +182,7 @@ let resolveFromCompilerPath = (~env, ~getModule, path) => { switch (getModule(moduleName)) { | None => None | Some(file) => - let env = {file, exported: file.contents.exported}; + let env = fileEnv(file); resolvePath(~env, ~getModule, ~path); }; switch (res) { diff --git a/src/rescript-editor-support/References.re b/src/rescript-editor-support/References.re index 13407c0c..8322ab29 100644 --- a/src/rescript-editor-support/References.re +++ b/src/rescript-editor-support/References.re @@ -115,7 +115,7 @@ let definedForLoc = (~file, ~getModule, locKind) => { let%try file = getModule(moduleName) |> RResult.orError("Cannot get module " ++ moduleName); - let env = {Query.file, exported: file.contents.exported}; + let env = Query.fileEnv(file); let%try (env, name) = Query.resolvePath(~env, ~path, ~getModule) |> RResult.orError("Cannot resolve path " ++ pathToString(path)); @@ -176,7 +176,7 @@ let resolveModuleReference = switch (declared.item) { | Structure(_) => Some((file, Some(declared))) | Ident(path) => - let env = {Query.file, exported: file.contents.exported}; + let env = Query.fileEnv(file); switch (Query.fromCompilerPath(~env, path)) { | `Not_found => None | `Exported(env, name) => @@ -186,7 +186,7 @@ let resolveModuleReference = /* Some((env.file.uri, validateLoc(md.name.loc, md.extentLoc))) */ | `Global(moduleName, path) => let%opt file = getModule(moduleName); - let env = {file, Query.exported: file.contents.exported}; + let env = Query.fileEnv(file); let%opt (env, name) = Query.resolvePath(~env, ~getModule, ~path); let%opt stamp = Hashtbl.find_opt(env.exported.modules, name); let%opt md = Hashtbl.find_opt(env.file.stamps.modules, stamp); @@ -217,7 +217,7 @@ let forLocalStamp = stamp, tip, ) => { - let env = {Query.file, exported: file.contents.exported}; + let env = Query.fileEnv(file); open Infix; let%opt localStamp = switch (tip) { @@ -365,7 +365,7 @@ let allReferencesForLoc = let%try file = getModule(moduleName) |> RResult.orError("Cannot get module " ++ moduleName); - let env = {Query.file, exported: file.contents.exported}; + let env = Query.fileEnv(file); let%try (env, name) = Query.resolvePath(~env, ~path, ~getModule) |> RResult.orError("Cannot resolve path " ++ pathToString(path)); @@ -512,7 +512,7 @@ let definitionForLoc = (~pathsForModule, ~file, ~getUri, ~getModule, loc) => { ++ tipToString(tip), ); let%opt file = getModule(moduleName); - let env = {Query.file, exported: file.contents.exported}; + let env = Query.fileEnv(file); let%opt (env, name) = Query.resolvePath(~env, ~path, ~getModule); let%opt stamp = Query.exportedForTip(~env, name, tip); /** oooh wht do I do if the stamp is inside a pseudo-file? */ From e2fd9fd02c1ccbb7b6452316e42bb90f979fe520 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Mon, 1 Feb 2021 17:48:20 +0100 Subject: [PATCH 3/5] First versioon of pipe autocompletion. --- src/rescript-editor-support/NewCompletions.re | 173 ++++++++++++++++-- 1 file changed, 155 insertions(+), 18 deletions(-) diff --git a/src/rescript-editor-support/NewCompletions.re b/src/rescript-editor-support/NewCompletions.re index 9e65d40f..55f1c009 100644 --- a/src/rescript-editor-support/NewCompletions.re +++ b/src/rescript-editor-support/NewCompletions.re @@ -651,24 +651,161 @@ let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => { ) ); - | Some((_text, _offset, Some(Cpipe(s)))) => [ - mkItem( - ~name="Foo.pipe", - ~kind=9, - ~detail=s, - ~docstring=None, - ~uri="uri", - ~pos_lnum=0, - ), - mkItem( - ~name="Foo.pine", - ~kind=9, - ~detail=s, - ~docstring=None, - ~uri="uri", - ~pos_lnum=0, - ), - ] + | Some((text, offset, Some(Cpipe(s)))) => + let rawOpens = PartialParser.findOpens(text, offset); + let allModules = + package.TopTypes.localModules @ package.dependencyModules; + let parts = Str.split(Str.regexp_string("->"), s); + let items = + getItems( + ~full, + ~package, + ~rawOpens, + ~getModule=State.fileForModule(state, ~package), + ~allModules, + ~pos, + ~parts, + ); + + let removePackageOpens = modulePath => + switch (modulePath) { + | [toplevel, ...rest] when package.opens |> List.mem(toplevel) => rest + | _ => modulePath + }; + + let rec removeRawOpen = (rawOpen, modulePath) => + switch (rawOpen, modulePath) { + | (Tip(_), _) => Some(modulePath) + | (Nested(s, inner), [first, ...restPath]) when s == first => + removeRawOpen(inner, restPath) + | _ => None + }; + + let rec removeRawOpens = (rawOpens, modulePath) => + switch (rawOpens) { + | [rawOpen, ...restOpens] => + let newModulePath = + switch (removeRawOpen(rawOpen, modulePath)) { + | None => modulePath + | Some(newModulePath) => newModulePath + }; + removeRawOpens(restOpens, newModulePath); + | [] => modulePath + }; + + let pipeItems = + switch (items) { + | [(uri, {SharedTypes.item: Value(t)}), ..._] => + let getModulePath = path => { + let rec loop = (path: Path.t) => + switch (path) { + | Pident(id) => [Ident.name(id)] + | Pdot(p, s, _) => [s, ...loop(p)] + | Papply(_) => [] + }; + switch (loop(path)) { + | [_, ...rest] => List.rev(rest) + | [] => [] + }; + }; + let modulePath = + switch (t.desc) { + | Tconstr(path, _, _) => getModulePath(path) + | Tlink({desc: Tconstr(path, _, _)}) => getModulePath(path) + | _ => [] + }; + switch (modulePath) { + | [_, ..._] => + let modulePathMinusOpens = + modulePath + |> removePackageOpens + |> removeRawOpens(rawOpens) + |> String.concat("."); + let completionName = name => + modulePathMinusOpens == "" + ? name : modulePathMinusOpens ++ "." ++ name; + let parts = modulePath @ [""]; + let items = + getItems( + ~full, + ~package, + ~rawOpens, + ~getModule=State.fileForModule(state, ~package), + ~allModules, + ~pos, + ~parts, + ); + items + |> List.filter(((_, {item})) => + switch (item) { + | Value(_) => true + | _ => false + } + ) + |> List.map( + ( + ( + uri, + { + SharedTypes.name: { + txt: name, + loc: {loc_start: {pos_lnum}}, + }, + docstring, + item, + }, + ), + ) => + mkItem( + ~name=completionName(name), + ~kind=kindToInt(item), + ~detail=detail(name, item), + ~docstring, + ~uri, + ~pos_lnum, + ) + ); + + | path => [ + mkItem( + ~name= + "can't complete type: " + ++ Shared.typeToString(t) + ++ " in: " + ++ (path |> String.concat(".")), + ~kind=9, + ~detail=s, + ~docstring=None, + ~uri, + ~pos_lnum=0, + ), + ] + }; + + | _ => [] + }; + + // let dummyItems = [ + // mkItem( + // ~name="Foo.pipe", + // ~kind=9, + // ~detail=s, + // ~docstring=None, + // ~uri="uri", + // ~pos_lnum=0, + // ), + // mkItem( + // ~name="Foo.pine", + // ~kind=9, + // ~detail=s, + // ~docstring=None, + // ~uri="uri", + // ~pos_lnum=0, + // ), + // ]; + let dummyItems = []; + + dummyItems @ pipeItems; | Some((_, _, Some(Clabel(_)))) => // not supported yet From d33ed05f7af51a32b96d6f2b39b8416bff6e11d4 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 3 Feb 2021 16:54:05 +0100 Subject: [PATCH 4/5] rebase on master --- src/rescript-editor-support/NewCompletions.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rescript-editor-support/NewCompletions.re b/src/rescript-editor-support/NewCompletions.re index 55f1c009..96e5d5df 100644 --- a/src/rescript-editor-support/NewCompletions.re +++ b/src/rescript-editor-support/NewCompletions.re @@ -588,7 +588,7 @@ let mkItem = (~name, ~kind, ~detail, ~docstring, ~uri, ~pos_lnum) => { J.s( (docstring |? "No docs") ++ "\n\n" - ++ uri + ++ Uri2.toString(uri) ++ ":" ++ string_of_int(pos_lnum), ), From 1f5b4f9e2a9acdf7edc1ad2d995563c701131cb3 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 4 Feb 2021 15:24:05 +0100 Subject: [PATCH 5/5] Keep completing when changing an existing prefix after ->, and cleanup. --- src/rescript-editor-support/NewCompletions.re | 187 ++++++++---------- 1 file changed, 79 insertions(+), 108 deletions(-) diff --git a/src/rescript-editor-support/NewCompletions.re b/src/rescript-editor-support/NewCompletions.re index 96e5d5df..8280410b 100644 --- a/src/rescript-editor-support/NewCompletions.re +++ b/src/rescript-editor-support/NewCompletions.re @@ -655,8 +655,8 @@ let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => { let rawOpens = PartialParser.findOpens(text, offset); let allModules = package.TopTypes.localModules @ package.dependencyModules; - let parts = Str.split(Str.regexp_string("->"), s); - let items = + + let getItems = parts => getItems( ~full, ~package, @@ -667,6 +667,23 @@ let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => { ~parts, ); + let getLhsType = (~lhs, ~partialName) => { + switch (getItems([lhs])) { + | [(_uri, {SharedTypes.item: Value(t)}), ..._] => + Some((t, partialName)) + | _ => None + }; + }; + + let lhsType = + switch (Str.split(Str.regexp_string("->"), s)) { + | [lhs] => getLhsType(~lhs, ~partialName="") + | [lhs, partialName] => getLhsType(~lhs, ~partialName) + | _ => + // Only allow one -> + None + }; + let removePackageOpens = modulePath => switch (modulePath) { | [toplevel, ...rest] when package.opens |> List.mem(toplevel) => rest @@ -693,119 +710,73 @@ let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => { | [] => modulePath }; - let pipeItems = - switch (items) { - | [(uri, {SharedTypes.item: Value(t)}), ..._] => - let getModulePath = path => { - let rec loop = (path: Path.t) => - switch (path) { - | Pident(id) => [Ident.name(id)] - | Pdot(p, s, _) => [s, ...loop(p)] - | Papply(_) => [] - }; - switch (loop(path)) { - | [_, ...rest] => List.rev(rest) - | [] => [] + switch (lhsType) { + | Some((t, partialName)) => + let getModulePath = path => { + let rec loop = (path: Path.t) => + switch (path) { + | Pident(id) => [Ident.name(id)] + | Pdot(p, s, _) => [s, ...loop(p)] + | Papply(_) => [] }; + switch (loop(path)) { + | [_, ...rest] => List.rev(rest) + | [] => [] }; - let modulePath = - switch (t.desc) { - | Tconstr(path, _, _) => getModulePath(path) - | Tlink({desc: Tconstr(path, _, _)}) => getModulePath(path) - | _ => [] - }; - switch (modulePath) { - | [_, ..._] => - let modulePathMinusOpens = - modulePath - |> removePackageOpens - |> removeRawOpens(rawOpens) - |> String.concat("."); - let completionName = name => - modulePathMinusOpens == "" - ? name : modulePathMinusOpens ++ "." ++ name; - let parts = modulePath @ [""]; - let items = - getItems( - ~full, - ~package, - ~rawOpens, - ~getModule=State.fileForModule(state, ~package), - ~allModules, - ~pos, - ~parts, - ); - items - |> List.filter(((_, {item})) => - switch (item) { - | Value(_) => true - | _ => false - } - ) - |> List.map( + }; + let modulePath = + switch (t.desc) { + | Tconstr(path, _, _) => getModulePath(path) + | Tlink({desc: Tconstr(path, _, _)}) => getModulePath(path) + | _ => [] + }; + switch (modulePath) { + | [_, ..._] => + let modulePathMinusOpens = + modulePath + |> removePackageOpens + |> removeRawOpens(rawOpens) + |> String.concat("."); + let completionName = name => + modulePathMinusOpens == "" + ? name : modulePathMinusOpens ++ "." ++ name; + let parts = modulePath @ [partialName]; + let items = getItems(parts); + items + |> List.filter(((_, {item})) => + switch (item) { + | Value(_) => true + | _ => false + } + ) + |> List.map( + ( ( - ( - uri, - { - SharedTypes.name: { - txt: name, - loc: {loc_start: {pos_lnum}}, - }, - docstring, - item, + uri, + { + SharedTypes.name: { + txt: name, + loc: {loc_start: {pos_lnum}}, }, - ), - ) => - mkItem( - ~name=completionName(name), - ~kind=kindToInt(item), - ~detail=detail(name, item), - ~docstring, - ~uri, - ~pos_lnum, - ) - ); - - | path => [ - mkItem( - ~name= - "can't complete type: " - ++ Shared.typeToString(t) - ++ " in: " - ++ (path |> String.concat(".")), - ~kind=9, - ~detail=s, - ~docstring=None, - ~uri, - ~pos_lnum=0, - ), - ] - }; + docstring, + item, + }, + ), + ) => + mkItem( + ~name=completionName(name), + ~kind=kindToInt(item), + ~detail=detail(name, item), + ~docstring, + ~uri, + ~pos_lnum, + ) + ); | _ => [] }; - - // let dummyItems = [ - // mkItem( - // ~name="Foo.pipe", - // ~kind=9, - // ~detail=s, - // ~docstring=None, - // ~uri="uri", - // ~pos_lnum=0, - // ), - // mkItem( - // ~name="Foo.pine", - // ~kind=9, - // ~detail=s, - // ~docstring=None, - // ~uri="uri", - // ~pos_lnum=0, - // ), - // ]; - let dummyItems = []; - - dummyItems @ pipeItems; + | None => [] + }; | Some((_, _, Some(Clabel(_)))) => // not supported yet