Skip to content

Commit 8f9402f

Browse files
committed
WIP autocomplete for JSX props.
Autocomplete for JSX props is only distinguished from autocomplete for identifiers by the context in which it takes place. Compared to completion of labeled arguments, it is not triggered by any special characters such as `~`. The context is used as a heuristic to interpret the **intention**. So for example, this should complete as a prop from `Comp`: ```rescript <Comp foo ``` but this should complete as an identifier: ```rescript <Comp x=foo ``` this should also complete as an identifier: ```rescript <Comp List.m ``` as even though it's in a place where a prop name is expected, the intent is clearly to complete a value from module `List`. This is a WIP to define what the contexts are.
1 parent 8f5c769 commit 8f9402f

File tree

2 files changed

+119
-16
lines changed

2 files changed

+119
-16
lines changed

src/rescript-editor-support/NewCompletions.re

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -614,14 +614,26 @@ let processCompletable =
614614
~package,
615615
~state,
616616
~pos,
617-
~text,
618-
~offset,
617+
~rawOpens,
618+
~allModules,
619619
completable: PartialParser.completable,
620620
) =>
621621
switch (completable) {
622+
| Cjsx(componentName, id) =>
623+
["first", "second"]
624+
|> List.map(name =>
625+
mkItem(
626+
~name=name ++ " " ++ componentName ++ " " ++ id,
627+
~kind=4,
628+
~deprecated=None,
629+
~detail="",
630+
~docstring=None,
631+
~uri=full.file.uri,
632+
~pos_lnum=fst(pos),
633+
)
634+
)
635+
622636
| Cpath(parts) =>
623-
let rawOpens = PartialParser.findOpens(text, offset);
624-
let allModules = package.TopTypes.localModules @ package.dependencyModules;
625637
let items =
626638
getItems(
627639
~full,
@@ -659,9 +671,6 @@ let processCompletable =
659671
);
660672

661673
| Cpipe(s) =>
662-
let rawOpens = PartialParser.findOpens(text, offset);
663-
let allModules = package.TopTypes.localModules @ package.dependencyModules;
664-
665674
let getItems = parts =>
666675
getItems(
667676
~full,
@@ -830,9 +839,6 @@ let processCompletable =
830839
|> List.map(mkDecorator);
831840

832841
| Clabel(funPath, prefix) =>
833-
let rawOpens = PartialParser.findOpens(text, offset);
834-
let allModules = package.TopTypes.localModules @ package.dependencyModules;
835-
836842
let getItems = parts =>
837843
getItems(
838844
~full,
@@ -890,8 +896,18 @@ let computeCompletions = (~full, ~maybeText, ~package, ~pos, ~state) => {
890896
switch (PartialParser.findCompletable(text, offset)) {
891897
| None => []
892898
| Some(completable) =>
899+
let rawOpens = PartialParser.findOpens(text, offset);
900+
let allModules =
901+
package.TopTypes.localModules @ package.dependencyModules;
893902
completable
894-
|> processCompletable(~full, ~package, ~state, ~pos, ~text, ~offset)
903+
|> processCompletable(
904+
~full,
905+
~package,
906+
~state,
907+
~pos,
908+
~rawOpens,
909+
~allModules,
910+
);
895911
}
896912
}
897913
};

src/rescript-editor-support/PartialParser.re

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,91 @@ let findCallFromArgument = (text, offset) => {
8787
loop(~i=offset, ~nClosed=0);
8888
};
8989

90+
// Figure out whether id should be autocompleted as component prop.
91+
// Find JSX context ctx for component M to autocomplete id (already parsed) as a prop.
92+
// ctx ::= <M args id
93+
// arg ::= id | id = [?] val
94+
// val ::= id | "abc" | 42 | {...} | (...) | [...]
95+
let findJsxContext = (text, offset) => {
96+
let rec loop = i => {
97+
let i = skipWhite(text, i);
98+
if (i > 0) {
99+
switch (text.[i]) {
100+
| '}' =>
101+
let i1 = findBackSkippingCommentsAndStrings(text, '{', '}', i - 1, 0);
102+
i1 > 0 ? beforeValue(i1) : None;
103+
| ')' =>
104+
let i1 = findBackSkippingCommentsAndStrings(text, '(', ')', i - 1, 0);
105+
i1 > 0 ? beforeValue(i1) : None;
106+
| ']' =>
107+
let i1 = findBackSkippingCommentsAndStrings(text, '[', ']', i - 1, 0);
108+
i1 > 0 ? beforeValue(i1) : None;
109+
| '"' =>
110+
let i1 = findBack(text, '"', i - 1);
111+
i1 > 0 ? beforeValue(i1) : None;
112+
| _ =>
113+
let i1 = startOfLident(text, i);
114+
let ident = String.sub(text, i1, i - i1 + 1);
115+
if (i1 >= 1 && ident != "") {
116+
switch (ident.[0]) {
117+
| 'A'..'Z' when i1 >= 1 && text.[i1 - 1] == '<' => Some(ident)
118+
| _ => beforeIdent(i1 - 1)
119+
};
120+
} else {
121+
None;
122+
};
123+
};
124+
} else {
125+
None;
126+
};
127+
}
128+
and beforeIdent = i => {
129+
let i = skipWhite(text, i);
130+
if (i > 0) {
131+
switch (text.[i]) {
132+
| '?' => fromEquals(i - 1)
133+
| '=' => fromEquals(i)
134+
| _ => loop(i - 1)
135+
};
136+
} else {
137+
None;
138+
};
139+
}
140+
and beforeValue = i => {
141+
let i = skipWhite(text, i);
142+
if (i > 0) {
143+
switch (text.[i]) {
144+
| '?' => fromEquals(i - 1)
145+
| _ => fromEquals(i)
146+
};
147+
} else {
148+
None;
149+
};
150+
}
151+
and fromEquals = i => {
152+
let i = skipWhite(text, i);
153+
if (i > 0) {
154+
switch (text.[i]) {
155+
| '=' =>
156+
let i = skipWhite(text, i - 1);
157+
let i1 = startOfLident(text, i);
158+
let ident = String.sub(text, i1, i - i1 + 1);
159+
ident == "" ? None : loop(i1 - 1);
160+
| _ => None
161+
};
162+
} else {
163+
None;
164+
};
165+
};
166+
loop(offset);
167+
};
168+
90169
type completable =
91-
| Cdecorator(string)
92-
| Clabel(list(string), string)
93-
| Cpath(list(string))
94-
| Cpipe(string);
170+
| Cdecorator(string) // e.g. @module
171+
| Clabel(list(string), string) // e.g. (["M", "foo"], "label") for M.foo(...~label...)
172+
| Cpath(list(string)) // e.g. ["M", "foo"] for M.foo
173+
| Cjsx(string, string) // E.g. ["M", "id"] for <M ... id
174+
| Cpipe(string); // E.g. "x->foo"
95175

96176
let findCompletable = (text, offset) => {
97177
let mkPath = s => {
@@ -105,7 +185,14 @@ let findCompletable = (text, offset) => {
105185
} else {
106186
let parts = Str.split(Str.regexp_string("."), s);
107187
let parts = s.[len - 1] == '.' ? parts @ [""] : parts;
108-
Cpath(parts);
188+
switch (parts) {
189+
| [id] when String.lowercase_ascii(id) == id =>
190+
switch (findJsxContext(text, offset - len - 1)) {
191+
| None => Cpath(parts)
192+
| Some(componentName) => Cjsx(componentName, id)
193+
}
194+
| _ => Cpath(parts)
195+
};
109196
};
110197
};
111198

0 commit comments

Comments
 (0)