Skip to content

Commit

Permalink
Support for inherited document symbols. (eclipse-jdtls#3378)
Browse files Browse the repository at this point in the history
- Integrate extended document symbols into DocumentSymbolHandler
- New method request 'java/extendedDocumentSymbol' is added to support
  reading inherited document symbols from a type declaration
- Do not report members of java.lang.Object

Co-authored-by: Roland Grunberg <[email protected]>
Co-authored-by: Gayan Perera <[email protected]>
  • Loading branch information
rgrunber and gayanper authored Feb 10, 2025
1 parent 76ad343 commit 4023575
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import org.eclipse.jdt.ls.core.internal.JDTUtils.LocationType;
import org.eclipse.jdt.ls.core.internal.JSONUtility;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.handlers.DocumentSymbolHandler;
import org.eclipse.jdt.ls.core.internal.handlers.SymbolUtils;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
Expand Down Expand Up @@ -184,7 +184,7 @@ private static TypeHierarchyItem toTypeHierarchyItem(IMember member, boolean exc
item.setDetail(packageFragment.getElementName());
}
}
item.setKind(excludeMember ? SymbolKind.Null : DocumentSymbolHandler.mapKind(type));
item.setKind(excludeMember ? SymbolKind.Null : SymbolUtils.mapKind(type));
item.setDeprecated(JDTUtils.isDeprecated(member));
Map<String, String> data = new HashMap<>();
data.put("element", member.getHandleIdentifier());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ private CallHierarchyItem toCallHierarchyItem(IMember member) throws JavaModelEx
String uri = fullLocation.getUri();
CallHierarchyItem item = new CallHierarchyItem();
item.setName(JDTUtils.getName(member));
item.setKind(DocumentSymbolHandler.mapKind(member));
item.setKind(SymbolUtils.mapKind(member));
item.setRange(range);
item.setSelectionRange(getLocation(member, LocationType.NAME_RANGE).getRange());
item.setUri(uri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@
import static org.eclipse.jdt.core.IJavaElement.PACKAGE_DECLARATION;
import static org.eclipse.jdt.core.IJavaElement.PACKAGE_FRAGMENT;
import static org.eclipse.jdt.core.IJavaElement.TYPE;
import static org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore.ALL_DEFAULT;
import static org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore.M_APP_RETURNTYPE;
import static org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore.ROOT_VARIABLE;
import static org.eclipse.jdt.ls.core.internal.JDTUtils.LocationType.FULL_RANGE;
import static org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore.F_POST_QUALIFIED;
import static org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore.M_POST_QUALIFIED;
import static org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin.logInfo;
import static org.eclipse.jdt.ls.core.internal.handlers.SymbolUtils.filter;
import static org.eclipse.jdt.ls.core.internal.handlers.SymbolUtils.getDetail;
import static org.eclipse.jdt.ls.core.internal.handlers.SymbolUtils.getName;
import static org.eclipse.jdt.ls.core.internal.handlers.SymbolUtils.getRange;
import static org.eclipse.jdt.ls.core.internal.handlers.SymbolUtils.getSelectionRange;
import static org.eclipse.jdt.ls.core.internal.handlers.SymbolUtils.mapKind;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -39,15 +43,12 @@
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
Expand Down Expand Up @@ -90,19 +91,28 @@
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.SymbolTag;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.Pure;

public class DocumentSymbolHandler {
private static Range DEFAULT_RANGE = new Range(new Position(0, 0), new Position(0, 0));

private PreferenceManager preferenceManager;
PreferenceManager preferenceManager;

private static IScanner fScanner;

public DocumentSymbolHandler(PreferenceManager preferenceManager) {
this.preferenceManager = preferenceManager;
}

public List<ExtendedDocumentSymbol> extendedDocumentSymbol(DocumentSymbolParams params, IProgressMonitor monitor) {
ITypeRoot unit = JDTUtils.resolveTypeRoot(params.getTextDocument().getUri());
if (unit == null || !unit.exists()) {
return Collections.emptyList();
}
List<DocumentSymbol> symbols = getHierarchicalOutline(unit, monitor, true);
return symbols.stream().map(s -> (ExtendedDocumentSymbol) s).toList();
}

public List<Either<SymbolInformation, DocumentSymbol>> documentSymbol(DocumentSymbolParams params, IProgressMonitor monitor) {

ITypeRoot unit = JDTUtils.resolveTypeRoot(params.getTextDocument().getUri());
Expand All @@ -111,7 +121,7 @@ public List<Either<SymbolInformation, DocumentSymbol>> documentSymbol(DocumentSy
}

if (preferenceManager.getClientPreferences().isHierarchicalDocumentSymbolSupported()) {
List<DocumentSymbol> symbols = this.getHierarchicalOutline(unit, monitor);
List<DocumentSymbol> symbols = this.getHierarchicalOutline(unit, monitor, false);
return symbols.stream().map(Either::<SymbolInformation, DocumentSymbol>forRight).collect(toList());
} else {
SymbolInformation[] elements = this.getOutline(unit, monitor);
Expand Down Expand Up @@ -178,7 +188,7 @@ private void collectChildren(ITypeRoot unit, IJavaElement[] elements, ArrayList<
}
}

private List<DocumentSymbol> getHierarchicalOutline(ITypeRoot unit, IProgressMonitor monitor) {
List<DocumentSymbol> getHierarchicalOutline(ITypeRoot unit, IProgressMonitor monitor, boolean includeInherited) {
try {
if (unit instanceof IClassFile && unit.getSourceRange() == null) { // no source attached
return getHierarchicalOutlineFromDecompiledSource(unit, monitor);
Expand All @@ -197,7 +207,7 @@ private List<DocumentSymbol> getHierarchicalOutline(ITypeRoot unit, IProgressMon
scanner.resetTo(shift, shift + sourceRange.getLength());
}
}
return childrenStream.map(child -> toDocumentSymbol(child, unit, monitor)).filter(Objects::nonNull).collect(Collectors.toList());
return childrenStream.map(child -> toDocumentSymbol(child, unit, monitor, includeInherited)).filter(Objects::nonNull).collect(Collectors.toList());
} catch (OperationCanceledException e) {
logInfo("User abort while collecting the document symbols.");
} catch (JavaModelException e) {
Expand All @@ -210,7 +220,7 @@ private List<DocumentSymbol> getHierarchicalOutline(ITypeRoot unit, IProgressMon
return emptyList();
}

private DocumentSymbol toDocumentSymbol(IJavaElement unit, ITypeRoot root, IProgressMonitor monitor) {
private DocumentSymbol toDocumentSymbol(IJavaElement unit, ITypeRoot root, IProgressMonitor monitor, boolean includeInherited) {
int type = unit.getElementType();
if (type != TYPE && type != FIELD && type != METHOD && type != PACKAGE_DECLARATION && type != COMPILATION_UNIT && type != PACKAGE_FRAGMENT) {
return null;
Expand All @@ -221,7 +231,7 @@ private DocumentSymbol toDocumentSymbol(IJavaElement unit, ITypeRoot root, IProg
if (monitor.isCanceled()) {
throw new OperationCanceledException("User abort");
}
DocumentSymbol symbol = new DocumentSymbol();
DocumentSymbol symbol = includeInherited ? new ExtendedDocumentSymbol() : new DocumentSymbol();
try {
String name = getName(unit);
symbol.setName(name);
Expand Down Expand Up @@ -256,12 +266,35 @@ private DocumentSymbol toDocumentSymbol(IJavaElement unit, ITypeRoot root, IProg
symbol.setDeprecated(true);
}
}
symbol.setDetail(getDetail(unit, name));
if (symbol instanceof ExtendedDocumentSymbol) {
symbol.setDetail(getDetail(unit, name, M_POST_QUALIFIED | F_POST_QUALIFIED));
} else {
symbol.setDetail(getDetail(unit, name));
}
if (unit instanceof IParent parent) {
List<IJavaElement> children = new ArrayList<>();
children.addAll(Arrays.asList(filter(parent.getChildren())));
if (symbol instanceof ExtendedDocumentSymbol) {
Location loc = JDTUtils.toLocation(unit);
if (loc != null) {
((ExtendedDocumentSymbol) symbol).setUri(loc.getUri());
}
if (unit instanceof IType tp) {
ITypeHierarchy supertypeHierarchy = tp.newSupertypeHierarchy(monitor);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
IType[] superClasses = supertypeHierarchy.getAllSuperclasses(tp);
for (IType superType : superClasses) {
if (!"java.lang.Object".equals(superType.getFullyQualifiedName())) {
children.addAll(Arrays.asList(filter(superType.getChildren())));
}
}
}
}
//@formatter:off
IJavaElement[] children = filter(parent.getChildren());
symbol.setChildren(Stream.of(children)
.map(child -> toDocumentSymbol(child, null, monitor))
symbol.setChildren(children.stream()
.map(child -> toDocumentSymbol(child, null, monitor, includeInherited))
.filter(Objects::nonNull)
.collect(Collectors.toList()));
//@formatter:off
Expand All @@ -272,120 +305,6 @@ private DocumentSymbol toDocumentSymbol(IJavaElement unit, ITypeRoot root, IProg
return symbol;
}

private String getName(IJavaElement element) {
String name = JavaElementLabelsCore.getElementLabel(element, ALL_DEFAULT);
return name == null ? element.getElementName() : name;
}

private Range getRange(IJavaElement element) throws JavaModelException {
Location location = JDTUtils.toLocation(element, FULL_RANGE);
return location == null ? DEFAULT_RANGE : location.getRange();
}

private Range getSelectionRange(IJavaElement element) throws JavaModelException {
Location location = JDTUtils.toLocation(element);
return location == null ? DEFAULT_RANGE : location.getRange();
}

private String getDetail(IJavaElement element, String name) {
String nameWithDetails = JavaElementLabelsCore.getElementLabel(element, ALL_DEFAULT | M_APP_RETURNTYPE | ROOT_VARIABLE);
if (nameWithDetails != null && nameWithDetails.startsWith(name)) {
return nameWithDetails.substring(name.length());
}
return "";
}

private IJavaElement[] filter(IJavaElement[] elements) {
return Stream.of(elements)
.filter(e -> (!isInitializer(e) && !isSyntheticElement(e)))
.toArray(IJavaElement[]::new);
}

private boolean isInitializer(IJavaElement element) {
if (element.getElementType() == IJavaElement.METHOD) {
String name = element.getElementName();
if ((name != null && name.indexOf('<') >= 0)) {
return true;
}
}
return false;
}

private boolean isSyntheticElement(IJavaElement element) {
if (!(element instanceof IMember)) {
return false;
}
IMember member= (IMember)element;
if (!(member.isBinary())) {
return false;
}
try {
return Flags.isSynthetic(member.getFlags());
} catch (JavaModelException e) {
return false;
}
}

public static SymbolKind mapKind(IJavaElement element) {
switch (element.getElementType()) {
case IJavaElement.TYPE:
try {
IType type = (IType)element;
if (type.isInterface()) {
return SymbolKind.Interface;
}
else if (type.isEnum()) {
return SymbolKind.Enum;
}
} catch (JavaModelException ignore) {
}
return SymbolKind.Class;
case IJavaElement.ANNOTATION:
return SymbolKind.Property; // TODO: find a better mapping
case IJavaElement.CLASS_FILE:
case IJavaElement.COMPILATION_UNIT:
return SymbolKind.File;
case IJavaElement.FIELD:
IField field = (IField) element;
try {
if (field.isEnumConstant()) {
return SymbolKind.EnumMember;
}
int flags = field.getFlags();
if (Flags.isStatic(flags) && Flags.isFinal(flags)) {
return SymbolKind.Constant;
}
} catch (JavaModelException ignore) {
}
return SymbolKind.Field;
case IJavaElement.IMPORT_CONTAINER:
case IJavaElement.IMPORT_DECLARATION:
//should we return SymbolKind.Namespace?
case IJavaElement.JAVA_MODULE:
return SymbolKind.Module;
case IJavaElement.INITIALIZER:
return SymbolKind.Constructor;
case IJavaElement.LOCAL_VARIABLE:
return SymbolKind.Variable;
case IJavaElement.TYPE_PARAMETER:
return SymbolKind.TypeParameter;
case IJavaElement.METHOD:
try {
// TODO handle `IInitializer`. What should be the `SymbolKind`?
if (element instanceof IMethod method && method.isConstructor()) {
return SymbolKind.Constructor;
}
return SymbolKind.Method;
} catch (JavaModelException e) {
return SymbolKind.Method;
}
case IJavaElement.PACKAGE_DECLARATION:
case IJavaElement.PACKAGE_FRAGMENT:
return SymbolKind.Package;
}
return SymbolKind.String;
}

private static IScanner getScanner() {
if (fScanner == null) {
fScanner = ToolFactory.createScanner(true, false, false, true);
Expand Down Expand Up @@ -660,4 +579,51 @@ private boolean containsModifier(List<?> modifiers, String target) {
return false;
}
}

public static class ExtendedDocumentSymbol extends DocumentSymbol {
/**
* The uri of the document of this symbol.
*/
@NonNull
private String uri;

/**
* The uri of the document of this symbol.
*/
public void setUri(@NonNull String uri) {
this.uri = uri;
}

/**
* The uri of the document of this symbol.
*/
@NonNull
@Pure
public String getUri() {
return this.uri;
}

@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(uri);
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ExtendedDocumentSymbol other = (ExtendedDocumentSymbol) obj;
return Objects.equals(uri, other.uri);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.eclipse.jdt.ls.core.internal.ServiceStatus;
import org.eclipse.jdt.ls.core.internal.codemanipulation.GenerateGetterSetterOperation.AccessorField;
import org.eclipse.jdt.ls.core.internal.handlers.CodeActionHandler.CodeActionData;
import org.eclipse.jdt.ls.core.internal.handlers.DocumentSymbolHandler.ExtendedDocumentSymbol;
import org.eclipse.jdt.ls.core.internal.handlers.ExtractInterfaceHandler.CheckExtractInterfaceResponse;
import org.eclipse.jdt.ls.core.internal.handlers.FindLinksHandler.FindLinksParams;
import org.eclipse.jdt.ls.core.internal.handlers.GenerateAccessorsHandler.AccessorCodeActionParams;
Expand Down Expand Up @@ -749,6 +750,16 @@ public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> docume
});
}

@Override
public CompletableFuture<List<ExtendedDocumentSymbol>> extendedDocumentSymbol(DocumentSymbolParams params) {
logInfo(">> java/extendedDocumentSymbol");
DocumentSymbolHandler handler = new DocumentSymbolHandler(preferenceManager);
return computeAsync((monitor) -> {
waitForLifecycleJobs(monitor);
return handler.extendedDocumentSymbol(params, monitor);
});
}

/* (non-Javadoc)
* @see org.eclipse.lsp4j.services.TextDocumentService#codeAction(org.eclipse.lsp4j.CodeActionParams)
*/
Expand Down
Loading

0 comments on commit 4023575

Please sign in to comment.