Skip to content
This repository was archived by the owner on Apr 24, 2021. It is now read-only.

Explore autocomplete for pipe-first. #59

Merged
merged 5 commits into from
Feb 8, 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
4 changes: 2 additions & 2 deletions src/rescript-editor-support/Hover.re
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
227 changes: 183 additions & 44 deletions src/rescript-editor-support/NewCompletions.re
Original file line number Diff line number Diff line change
Expand Up @@ -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"
++ Uri2.toString(uri)
++ ":"
++ string_of_int(pos_lnum),
),
),
]),
),
]);
};

let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => {
let parameters =
switch (maybeText) {
Expand All @@ -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;
Expand All @@ -609,45 +628,165 @@ 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)))) =>
let rawOpens = PartialParser.findOpens(text, offset);
let allModules =
package.TopTypes.localModules @ package.dependencyModules;

let getItems = parts =>
getItems(
~full,
~package,
~rawOpens,
~getModule=State.fileForModule(state, ~package),
~allModules,
~pos,
~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
| _ => 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
};

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 @ [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,
},
),
) =>
mkItem(
~name=completionName(name),
~kind=kindToInt(item),
~detail=detail(name, item),
~docstring,
~uri,
~pos_lnum,
)
);

| _ => []
};
| None => []
};

| 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;
};
};
19 changes: 15 additions & 4 deletions src/rescript-editor-support/PartialParser.re
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,31 @@ 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 => {
i < 0
? 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'
Expand Down
5 changes: 1 addition & 4 deletions src/rescript-editor-support/ProcessExtra.re
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
16 changes: 3 additions & 13 deletions src/rescript-editor-support/Query.re
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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);
};
};

Expand All @@ -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);
};
};
};
Expand Down Expand Up @@ -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) {
Expand Down
Loading