Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
49043ab
8285150: Improve tab completion for annotations
lahodaj Dec 19, 2024
b9e7d25
Merge branch 'master' into JDK-8285150
lahodaj Jan 31, 2025
7752140
A crude prototype of a more broad API for JShell's completion.
lahodaj Jun 3, 2025
b9c1b35
Fixing test.
lahodaj Jun 3, 2025
960f0d8
Merge branch 'master' into JDK-8285150
lahodaj Aug 14, 2025
1dd43af
Improving the handling of annotation attributes that are annotations …
lahodaj Aug 15, 2025
356a6d5
Merge branch 'master' into JDK-8285150-dev2
lahodaj Aug 15, 2025
becf80a
Merge branch 'master' into jshell-prototype-better-completion
lahodaj Aug 15, 2025
8775258
Merge branch 'JDK-8285150-dev2' into jshell-prototype-better-completion
lahodaj Aug 15, 2025
40f8870
Fixing completion prefix behavior
lahodaj Aug 18, 2025
2b51926
Merge branch 'JDK-8285150' into jshell-prototype-better-completion
lahodaj Aug 28, 2025
bbaf6ba
Starting with tests.
lahodaj Sep 3, 2025
31657d7
Fixing whitespace.
lahodaj Sep 3, 2025
6dee65f
Merge branch 'master' into JDK-8285150
lahodaj Sep 8, 2025
5c536f4
Merge branch 'JDK-8285150' into JDK-8366691
lahodaj Sep 8, 2025
2a57147
Merge branch 'master' into JDK-8285150
lahodaj Sep 8, 2025
35311d8
Adding Test annotation
lahodaj Sep 8, 2025
2310a71
Merge branch 'JDK-8285150' into JDK-8366691
lahodaj Sep 8, 2025
dfd7840
Adjust tests.
lahodaj Sep 8, 2025
346d04e
Fixing javadoc
lahodaj Sep 8, 2025
344aed2
Improving test.
lahodaj Sep 9, 2025
f865df1
Merge remote-tracking branch 'origin/JDK-8366691' into JDK-8366691
lahodaj Sep 9, 2025
f8c28cc
Fixing tests.
lahodaj Sep 10, 2025
40b03c8
Merge branch 'master' into JDK-8366691
lahodaj Sep 12, 2025
479dd27
Fixing(?) anchor.
lahodaj Sep 12, 2025
2814644
Ability to get real type.
lahodaj Sep 18, 2025
34437a7
8368848: JShell's code completion not always working for multi-snippe…
lahodaj Sep 29, 2025
32a71c1
Merge branch 'JDK-8368848' into JDK-8366691
lahodaj Sep 29, 2025
c41cf67
Merge branch 'master' into JDK-8366691
lahodaj Oct 2, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public static JavadocHelper create(JavacTask mainTask, Collection<? extends Path
StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
try {
fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourceLocations);
return new OnDemandJavadocHelper(mainTask, fm);
return new OnDemandJavadocHelper(mainTask, fm, sourceLocations);
} catch (IOException ex) {
try {
fm.close();
Expand All @@ -135,6 +135,21 @@ public Element getSourceElement(Element forElement) throws IOException {
}
@Override
public void close() throws IOException {}

@Override
public String getResolvedDocComment(StoredElement forElement) throws IOException {
return null;
}

@Override
public StoredElement getHandle(Element forElement) {
return null;
}

@Override
public Collection<? extends Path> getSourceLocations() {
return List.of();
}
};
}
}
Expand All @@ -147,6 +162,7 @@ public void close() throws IOException {}
* @throws IOException if something goes wrong in the search
*/
public abstract String getResolvedDocComment(Element forElement) throws IOException;
public abstract String getResolvedDocComment(StoredElement forElement) throws IOException;

/**Returns an element representing the same given program element, but the returned element will
* be resolved from source, if it can be found. Returns the original element if the source for
Expand All @@ -158,23 +174,30 @@ public void close() throws IOException {}
*/
public abstract Element getSourceElement(Element forElement) throws IOException;

public abstract StoredElement getHandle(Element forElement);
public abstract Collection<? extends Path> getSourceLocations();

/**Closes the helper.
*
* @throws IOException if something foes wrong during the close
*/
@Override
public abstract void close() throws IOException;

public record StoredElement(String module, String binaryName, String handle) {}

private static final class OnDemandJavadocHelper extends JavadocHelper {
private final JavacTask mainTask;
private final JavaFileManager baseFileManager;
private final StandardJavaFileManager fm;
private final Map<String, Pair<JavacTask, TreePath>> signature2Source = new HashMap<>();
private final Collection<? extends Path> sourceLocations;

private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm) {
private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm, Collection<? extends Path> sourceLocations) {
this.mainTask = mainTask;
this.baseFileManager = ((JavacTaskImpl) mainTask).getContext().get(JavaFileManager.class);
this.fm = fm;
this.sourceLocations = sourceLocations;
}

@Override
Expand All @@ -187,6 +210,16 @@ public String getResolvedDocComment(Element forElement) throws IOException {
return getResolvedDocComment(sourceElement.fst, sourceElement.snd);
}

@Override
public String getResolvedDocComment(StoredElement forElement) throws IOException {
Pair<JavacTask, TreePath> sourceElement = getSourceElement(forElement);

if (sourceElement == null)
return null;

return getResolvedDocComment(sourceElement.fst, sourceElement.snd);
}

@Override
public Element getSourceElement(Element forElement) throws IOException {
Pair<JavacTask, TreePath> sourceElement = getSourceElement(mainTask, forElement);
Expand All @@ -202,7 +235,30 @@ public Element getSourceElement(Element forElement) throws IOException {
return result;
}

private String getResolvedDocComment(JavacTask task, TreePath el) throws IOException {
@Override
public StoredElement getHandle(Element forElement) {
TypeElement type = topLevelType(forElement);

if (type == null)
return null;

Elements elements = mainTask.getElements();
ModuleElement module = elements.getModuleOf(type);
String moduleName = module == null || module.isUnnamed()
? null
: module.getQualifiedName().toString();
String binaryName = elements.getBinaryName(type).toString();
String handle = elementSignature(forElement);

return new StoredElement(moduleName, binaryName, handle);
}

@Override
public Collection<? extends Path> getSourceLocations() {
return sourceLocations;
}

private String getResolvedDocComment(JavacTask task, TreePath el) throws IOException {
DocTrees trees = DocTrees.instance(task);
Element element = trees.getElement(el);
String docComment = trees.getDocComment(el);
Expand Down Expand Up @@ -634,7 +690,7 @@ private Stream<ExecutableElement> superMethodsForInheritDoc(JavacTask task,
.filter(supMethod -> task.getElements().overrides(method, supMethod, type));
}

/* Find types from which methods in type may inherit javadoc, in the proper order.*/
/* Find types from which methods in binaryName may inherit javadoc, in the proper order.*/
private Stream<Element> superTypeForInheritDoc(JavacTask task, Element type) {
TypeElement clazz = (TypeElement) type;
Stream<Element> result = interfaces(clazz);
Expand Down Expand Up @@ -701,6 +757,35 @@ private String getThrownException(JavacTask task, TreePath rootOn, DocCommentTre
return exc != null ? exc.toString() : null;
}

private Pair<JavacTask, TreePath> getSourceElement(StoredElement el) throws IOException {
if (el == null) {
return null;
}

String handle = el.handle();
Pair<JavacTask, TreePath> cached = signature2Source.get(handle);

if (cached != null) {
return cached.fst != null ? cached : null;
}

Pair<JavacTask, CompilationUnitTree> source = findSource(el.module(), el.binaryName());

if (source == null)
return null;

fillElementCache(source.fst, source.snd);

cached = signature2Source.get(handle);

if (cached != null) {
return cached;
} else {
signature2Source.put(handle, Pair.of(null, null));
return null;
}
}

private Pair<JavacTask, TreePath> getSourceElement(JavacTask origin, Element el) throws IOException {
String handle = elementSignature(el);
Pair<JavacTask, TreePath> cached = signature2Source.get(handle);
Expand Down
147 changes: 147 additions & 0 deletions src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

/**
* Provides analysis utilities for source code input.
Expand Down Expand Up @@ -64,6 +70,18 @@ public abstract class SourceCodeAnalysis {
*/
public abstract List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);

/**
* Compute possible follow-ups for the given input.
* Uses information from the current {@code JShell} state, including
* type information, to filter the suggestions.
* @param input the user input, so far
* @param cursor the current position of the cursors in the given {@code input} text
* @param convertor convert the given {@linkplain ElementSuggestion} to a custom completion suggestions.
* @return list of candidate continuations of the given input.
* @since 26
*/
public abstract <S> List<S> completionSuggestions(String input, int cursor, ElementSuggestionConvertor<S> convertor);

/**
* Compute documentation for the given user's input. Multiple {@code Documentation} objects may
* be returned when multiple elements match the user's input (like for overloaded methods).
Expand Down Expand Up @@ -315,6 +333,135 @@ public interface Suggestion {
boolean matchesType();
}

/**
* A description of an {@linkplain Element} that is a possible continuation of
* a given snippet.
*
* @apiNote Instances of this interface and instances of the returned {@linkplain Elements}
* should only be used and held during the execution of the
* {@link #completionSuggestions(java.lang.String, int, jdk.jshell.SourceCodeAnalysis.ElementSuggestionConvertor) }
* method. Their use outside of the context of the method is not supported and
* the effect is undefined.
*
* @since 26
*/
public sealed interface ElementSuggestion permits SourceCodeAnalysisImpl.ElementSuggestionImpl {
/**
* {@return a possible continuation {@linkplain Element}, or {@code null}
* if this item does not represent an {@linkplain Element}.}
*/
Element element();
/**
* {@return a possible continuation keyword, or {@code null}
* if this item does not represent a keyword.}
*/
String keyword();
/**
* {@return {@code true} if this {@linkplain Element}'s type fits into
* the context.}
*
* Typically used when the type of the element fits the expected type.
*/
boolean matchesType();
/**
* {@return the offset in the original snippet at which point this {@linkplain Element}
* should be inserted.}
*/
int anchor();
/**
* {@return a {@linkplain Supplier} for the javadoc documentation for this Element.}
*
* @apiNote The instance returned from this method is safe to hold for extended
* periods of time, and can be called outside of the context of the
* {@link #completionSuggestions(java.lang.String, int, jdk.jshell.SourceCodeAnalysis.ElementSuggestionConvertor) } method.
*/
Supplier<String> documentation();
}

/**
* Permit access to completion state.
*
* @since 26
*/
public sealed interface CompletionState permits SourceCodeAnalysisImpl.CompletionStateImpl {
/**
* {@return true if the given element is available using the simple name at
* the place of the cursor.}
*
* @param el {@linkplain Element} to check
*/
public boolean availableUsingSimpleName(Element el);
/**
* {@return flags describing the overall completion context.}
*/
public Set<CompletionContext> completionContext();
/**
* {@return if the context is a qualified expression
* (i.e. {@link CompletionContext#QUALIFIED} is set),
* the type of the selector expression; {@code null} otherwise.}
*/
public TypeMirror selectorType();
/**
* {@return an implementation of some utility methods for
* operating on elements}
*/
Elements elementUtils();
/**
* {@return an implementation of some utility methods for
* operating on types}
*/
Types typeUtils();
}

/**
* Various flags describing the context in which the completion happens.
*
* @since 26
*/
public enum CompletionContext {
/**
* The context is inside annotation attributes.
*/
ANNOTATION_ATTRIBUTE,
/**
* Parentheses should not be filled for methods and constructor
* in the current context.
*
* Typically used in the import or method reference contexts.
*/
NO_PAREN,
/**
* Interpret {@link ElementKind#ANNOTATION_TYPE}s as annotation uses. Typically means
* they should be prefixed with {@code @}.
*/
TYPES_AS_ANNOTATIONS,
/**
* The context is in a qualified expression (like member access). Simple
* names only should be used.
*/
QUALIFIED,
;
}

/**
* A convertor from a list of {@linkplain ElementSuggestion} to a list
* of custom target completion items.
*
* @param <S> a custom target completion type.
* @since 26
*/
public interface ElementSuggestionConvertor<S> {
/**
* Convert a list of {@linkplain ElementSuggestion} to a list
* of custom completion items.
*
* @param state the state of the completion
* @param suggestions the input suggestions
* @return the converted suggestions
*/
public List<S> convert(CompletionState state, List<? extends ElementSuggestion> suggestions);
}

/**
* A documentation for a candidate for continuation of the given user's input.
*/
Expand Down
Loading