Skip to content

Commit 5251405

Browse files
committed
8368848: JShell's code completion not always working for multi-snippet inputs
Reviewed-by: asotona
1 parent fa3af82 commit 5251405

File tree

4 files changed

+125
-26
lines changed

4 files changed

+125
-26
lines changed

src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ private CompoundWrap wrappedInClass(String className, String imports, List<Wrap>
7373

7474
OuterWrap wrapInClass(Set<Key> except, Collection<Snippet> plus,
7575
List<Snippet> snippets, List<Wrap> wraps) {
76-
String imports = state.maps.packageAndImportsExcept(except, plus);
76+
List<String> extraImports =
77+
plus.stream()
78+
.map(psi -> psi.importLine(state))
79+
.toList();
80+
String imports = state.maps.packageAndImportsExcept(except, extraImports);
7781
// className is unique to the set of snippets and their version (seq)
7882
String className = REPL_CLASS_PREFIX + snippets.stream()
7983
.sorted((sn1, sn2) -> sn1.key().index() - sn2.key().index())
@@ -86,9 +90,16 @@ OuterWrap wrapInClass(Set<Key> except, Collection<Snippet> plus,
8690
}
8791

8892
OuterWrap wrapInTrialClass(Wrap wrap) {
89-
String imports = state.maps.packageAndImportsExcept(null, null);
93+
return wrapInTrialClass(List.of(), List.of(), wrap);
94+
}
95+
96+
OuterWrap wrapInTrialClass(List<String> extraImports, List<Wrap> preWraps, Wrap wrap) {
97+
String imports = state.maps.packageAndImportsExcept(null, extraImports);
98+
List<Wrap> allWraps = new ArrayList<>();
99+
allWraps.addAll(preWraps);
100+
allWraps.add(wrap);
90101
CompoundWrap w = wrappedInClass(REPL_DOESNOTMATTER_CLASS_NAME, imports,
91-
Collections.singletonList(wrap));
102+
allWraps);
92103
return new OuterWrap(w);
93104
}
94105

src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,15 @@ List<Snippet> snippetList() {
105105
return new ArrayList<>(snippets);
106106
}
107107

108-
String packageAndImportsExcept(Set<Key> except, Collection<Snippet> plus) {
108+
String packageAndImportsExcept(Set<Key> except, Collection<String> extraImports) {
109109
StringBuilder sb = new StringBuilder();
110110
sb.append("package ").append(REPL_PACKAGE).append(";\n");
111111
for (Snippet si : keyIndexToSnippet) {
112112
if (si != null && si.status().isDefined() && (except == null || !except.contains(si.key())) && si.name() != null && !si.name().isEmpty()) {
113113
sb.append(si.importLine(state));
114114
}
115115
}
116-
if (plus != null) {
117-
plus.forEach(psi -> sb.append(psi.importLine(state)));
118-
}
116+
extraImports.forEach(sb::append);
119117
return sb.toString();
120118
}
121119

src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java

Lines changed: 99 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -300,17 +300,8 @@ private List<Suggestion> completionSuggestionsImpl(String code, int cursor, int[
300300
identifier = m.group();
301301
}
302302
}
303-
code = code.substring(0, cursor);
304-
if (code.trim().isEmpty()) { //TODO: comment handling
305-
code += ";";
306-
}
307-
boolean[] moduleImport = new boolean[1];
308-
OuterWrap codeWrap = switch (guessKind(code, moduleImport)) {
309-
case IMPORT -> moduleImport[0] ? proc.outerMap.wrapImport(Wrap.simpleWrap(code), null)
310-
: proc.outerMap.wrapImport(Wrap.simpleWrap(code + "any.any"), null);
311-
case CLASS, METHOD -> proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(code));
312-
default -> proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
313-
};
303+
304+
OuterWrap codeWrap = wrapCodeForCompletion(code, cursor, true);
314305
String[] requiredPrefix = new String[] {identifier};
315306
return computeSuggestions(codeWrap, code, cursor, requiredPrefix, anchor).stream()
316307
.filter(s -> filteringText(s).startsWith(requiredPrefix[0]) && !s.continuation().equals(REPL_DOESNOTMATTER_CLASS_NAME))
@@ -1740,15 +1731,11 @@ public List<Documentation> documentation(String code, int cursor, boolean comput
17401731
};
17411732

17421733
private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) {
1743-
code = code.substring(0, cursor);
1744-
if (code.trim().isEmpty()) { //TODO: comment handling
1745-
code += ";";
1746-
}
1747-
1748-
if (guessKind(code) == Kind.IMPORT)
1734+
OuterWrap codeWrap = wrapCodeForCompletion(code, cursor, false);
1735+
if (codeWrap == null) {
1736+
//import:
17491737
return Collections.emptyList();
1750-
1751-
OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
1738+
}
17521739
return proc.taskFactory.analyze(codeWrap, List.of(keepParameterNames), at -> {
17531740
SourcePositions sp = at.trees().getSourcePositions();
17541741
CompilationUnitTree topLevel = at.firstCuTree();
@@ -2434,6 +2421,99 @@ public static void waitCurrentBackgroundTasksFinished() throws Exception {
24342421
INDEXER.submit(() -> {}).get();
24352422
}
24362423

2424+
private OuterWrap wrapCodeForCompletion(String code, int cursor, boolean wrapImports) {
2425+
code = code.substring(0, cursor);
2426+
if (code.trim().isEmpty()) { //TODO: comment handling
2427+
code += ";";
2428+
}
2429+
2430+
List<String> imports = new ArrayList<>();
2431+
List<Wrap> declarationParts = new ArrayList<>();
2432+
String lastImport = null;
2433+
boolean lastImportIsModuleImport = false;
2434+
Wrap declarationWrap = null;
2435+
Wrap pendingWrap = null;
2436+
String input = code;
2437+
boolean cont = true;
2438+
int startOffset = 0;
2439+
2440+
while (cont) {
2441+
if (lastImport != null) {
2442+
imports.add(lastImport);
2443+
lastImport = null;
2444+
}
2445+
if (declarationWrap != null) {
2446+
declarationParts.add(declarationWrap);
2447+
declarationWrap = null;
2448+
pendingWrap = null;
2449+
}
2450+
2451+
String current;
2452+
SourceCodeAnalysis.CompletionInfo completeness = analyzeCompletion(input);
2453+
int newStartOffset;
2454+
2455+
if (completeness.completeness().isComplete() && !completeness.remaining().isBlank()) {
2456+
current = input.substring(0, input.length() - completeness.remaining().length());
2457+
newStartOffset = startOffset + input.length() - completeness.remaining().length();
2458+
input = completeness.remaining();
2459+
cont = true;
2460+
} else {
2461+
current = input;
2462+
cont = false;
2463+
newStartOffset = startOffset;
2464+
}
2465+
2466+
boolean[] moduleImport = new boolean[1];
2467+
2468+
switch (guessKind(current, moduleImport)) {
2469+
case IMPORT -> {
2470+
lastImport = current;
2471+
lastImportIsModuleImport = moduleImport[0];
2472+
}
2473+
case CLASS, METHOD -> {
2474+
pendingWrap = declarationWrap = Wrap.classMemberWrap(whitespaces(code, startOffset) + current);
2475+
}
2476+
case VARIABLE -> {
2477+
declarationWrap = Wrap.classMemberWrap(whitespaces(code, startOffset) + current);
2478+
pendingWrap = Wrap.methodWrap(whitespaces(code, startOffset) + current);
2479+
}
2480+
default -> {
2481+
pendingWrap = declarationWrap = Wrap.methodWrap(whitespaces(code, startOffset) + current);
2482+
}
2483+
}
2484+
2485+
startOffset = newStartOffset;
2486+
}
2487+
2488+
if (lastImport != null) {
2489+
if (wrapImports) {
2490+
return proc.outerMap.wrapImport(Wrap.simpleWrap(whitespaces(code, startOffset) + lastImport + (!lastImportIsModuleImport ? "any.any" : "")), null);
2491+
} else {
2492+
return null;
2493+
}
2494+
}
2495+
2496+
if (pendingWrap != null) {
2497+
return proc.outerMap.wrapInTrialClass(imports, declarationParts, pendingWrap);
2498+
}
2499+
2500+
throw new IllegalStateException("No pending wrap for: " + code);
2501+
}
2502+
2503+
private static String whitespaces(String input, int offset) {
2504+
StringBuilder result = new StringBuilder();
2505+
2506+
for (int i = 0; i < offset; i++) {
2507+
if (input.charAt(i) == '\n') {
2508+
result.append('\n');
2509+
} else {
2510+
result.append(' ');
2511+
}
2512+
}
2513+
2514+
return result.toString();
2515+
}
2516+
24372517
/**
24382518
* A candidate for continuation of the given user's input.
24392519
*/

test/langtools/jdk/jshell/CompletionSuggestionTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,4 +941,14 @@ public void testAnnotation() {
941941
assertEval("import static java.lang.annotation.RetentionPolicy.*;");
942942
assertCompletion("@AnnA(C|", true, "CLASS");
943943
}
944+
945+
@Test
946+
public void testMultiSnippet() {
947+
assertCompletion("String s = \"\"; s.len|", true, "length()");
948+
assertCompletion("String s() { return \"\"; } s().len|", true, "length()");
949+
assertCompletion("String s() { return \"\"; } import java.util.List; List.o|", true, "of(");
950+
assertCompletion("String s() { return \"\"; } import java.ut| ", true, "util.");
951+
assertCompletion("class S { public int length() { return 0; } } new S().len|", true, "length()");
952+
assertSignature("void f() { } f(|", "void f()");
953+
}
944954
}

0 commit comments

Comments
 (0)