Skip to content

Commit

Permalink
Add support for interface and abstract method implementation code lens
Browse files Browse the repository at this point in the history
Signed-off-by: Hope Hadfield <[email protected]>
  • Loading branch information
hopehadfield authored and rgrunber committed Dec 17, 2024
1 parent 39a9fd8 commit 3a6cd74
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
Expand Down Expand Up @@ -103,11 +104,11 @@ public CodeLens resolve(CodeLens lens, IProgressMonitor monitor) {
JavaLanguageServerPlugin.logException(e.getMessage(), e);
}
} else if (IMPLEMENTATION_TYPE.equals(type)) {
if (element instanceof IType typeElement) {
if (element instanceof IType || element instanceof IMethod) {
try {
IDocument document = JsonRpcHelpers.toDocument(typeRoot.getBuffer());
int offset = document.getLineOffset(position.getLine()) + position.getCharacter();
locations = findImplementations(typeRoot, typeElement, offset, monitor);
locations = findImplementations(typeRoot, element, offset, monitor);
} catch (CoreException | BadLocationException e) {
JavaLanguageServerPlugin.logException(e.getMessage(), e);
}
Expand All @@ -128,12 +129,15 @@ public CodeLens resolve(CodeLens lens, IProgressMonitor monitor) {
return lens;
}

private List<Location> findImplementations(ITypeRoot root, IType type, int offset, IProgressMonitor monitor) throws CoreException {
private List<Location> findImplementations(ITypeRoot root, IJavaElement element, int offset, IProgressMonitor monitor) throws CoreException {
//java.lang.Object is a special case. We need to minimize heavy cost of I/O,
// by avoiding opening all files from the Object hierarchy
boolean useDefaultLocation = "java.lang.Object".equals(type.getFullyQualifiedName());
boolean useDefaultLocation = false;
if (element instanceof IType type) {
useDefaultLocation = "java.lang.Object".equals(type.getFullyQualifiedName());
}
ImplementationToLocationMapper mapper = new ImplementationToLocationMapper(preferenceManager.isClientSupportsClassFileContent(), useDefaultLocation);
ImplementationCollector<Location> searcher = new ImplementationCollector<>(root, new Region(offset, 0), type, mapper);
ImplementationCollector<Location> searcher = new ImplementationCollector<>(root, new Region(offset, 0), element, mapper);
return searcher.findImplementations(monitor);
}

Expand Down Expand Up @@ -228,14 +232,24 @@ private void collectCodeLenses(ITypeRoot typeRoot, IJavaElement[] elements, Coll
}
}
}
if (preferenceManager.getPreferences().isImplementationsCodeLensEnabled() && element instanceof IType type) {
String implementationsPreference = preferenceManager.getPreferences().getImplementationsCodeLens();
if (("all".equals(implementationsPreference) || "types".equals(implementationsPreference)) && element instanceof IType type) {
if (type.isInterface() || Flags.isAbstract(type.getFlags())) {
CodeLens lens = getCodeLens(IMPLEMENTATION_TYPE, element, typeRoot);
if (lens != null) {
lenses.add(lens);
}
}
}

if (("all".equals(implementationsPreference) || "methods".equals(implementationsPreference)) && element instanceof IMethod methodElement) {
if ((methodElement.getParent() instanceof IType parentType && parentType.isInterface()) || Flags.isAbstract(methodElement.getFlags())) {
CodeLens lens = getCodeLens(IMPLEMENTATION_TYPE, element, typeRoot);
if (lens != null) {
lenses.add(lens);
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@ public class Preferences {
public static final String REFERENCES_CODE_LENS_ENABLED_KEY = "java.referencesCodeLens.enabled";

/**
* Preference key to enable/disable implementation code lenses.
* Preference key to set implementation code lenses.
*/
public static final String IMPLEMENTATIONS_CODE_LENS_ENABLED_KEY = "java.implementationsCodeLens.enabled";
public static final String IMPLEMENTATIONS_CODE_LENS_KEY = "java.implementationCodeLens";

/**
* Preference key to enable/disable formatter.
Expand Down Expand Up @@ -628,7 +628,7 @@ public class Preferences {
private boolean mavenDownloadSources;
private boolean eclipseDownloadSources;
private boolean mavenUpdateSnapshots;
private boolean implementationsCodeLensEnabled;
private String implementationsCodeLens;
private boolean javaFormatEnabled;
private String javaQuickFixShowAt;
private boolean javaFormatOnTypeEnabled;
Expand Down Expand Up @@ -906,7 +906,7 @@ public Preferences() {
eclipseDownloadSources = false;
mavenUpdateSnapshots = false;
referencesCodeLensEnabled = true;
implementationsCodeLensEnabled = false;
implementationsCodeLens = "none";
javaFormatEnabled = true;
javaQuickFixShowAt = LINE;
javaFormatOnTypeEnabled = false;
Expand Down Expand Up @@ -1071,8 +1071,8 @@ public static Preferences createFrom(Map<String, Object> configuration) {
prefs.setMavenUpdateSnapshots(updateSnapshots);
boolean referenceCodelensEnabled = getBoolean(configuration, REFERENCES_CODE_LENS_ENABLED_KEY, true);
prefs.setReferencesCodelensEnabled(referenceCodelensEnabled);
boolean implementationCodeLensEnabled = getBoolean(configuration, IMPLEMENTATIONS_CODE_LENS_ENABLED_KEY, false);
prefs.setImplementationCodelensEnabled(implementationCodeLensEnabled);
String implementationCodeLens = getString(configuration, IMPLEMENTATIONS_CODE_LENS_KEY, "none");
prefs.setImplementationCodelens(implementationCodeLens);

boolean javaFormatEnabled = getBoolean(configuration, JAVA_FORMAT_ENABLED_KEY, true);
prefs.setJavaFormatEnabled(javaFormatEnabled);
Expand Down Expand Up @@ -1578,8 +1578,8 @@ public void setSignatureHelpDescriptionEnabled(boolean signatureHelpDescriptionE
this.signatureHelpDescriptionEnabled = signatureHelpDescriptionEnabled;
}

private Preferences setImplementationCodelensEnabled(boolean enabled) {
this.implementationsCodeLensEnabled = enabled;
private Preferences setImplementationCodelens(String implementationCodeLensOption) {
this.implementationsCodeLens = implementationCodeLensOption;
return this;
}

Expand Down Expand Up @@ -1908,12 +1908,12 @@ public boolean isMavenUpdateSnapshots() {
return mavenUpdateSnapshots;
}

public boolean isImplementationsCodeLensEnabled() {
return implementationsCodeLensEnabled;
public String getImplementationsCodeLens() {
return implementationsCodeLens;
}

public boolean isCodeLensEnabled() {
return referencesCodeLensEnabled || implementationsCodeLensEnabled;
return referencesCodeLensEnabled || !implementationsCodeLens.equals("none");
}

public boolean isJavaFormatEnabled() {
Expand Down
10 changes: 10 additions & 0 deletions org.eclipse.jdt.ls.tests/projects/eclipse/hello/src/java/Ext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package java;

public class Ext extends Test{

@Override
public void testMethod() {}

@Override
public void testAbstractMethod() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package java;

public interface ITest {

public void testMethod();

}
11 changes: 11 additions & 0 deletions org.eclipse.jdt.ls.tests/projects/eclipse/hello/src/java/Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package java;

public abstract class Test implements ITest {

@Override
public void testMethod() {}

public abstract void testAbstractMethod();

public void noReferences() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public void testGetCodeLensSymbols() throws Exception {
@Test
@SuppressWarnings("unchecked")
public void testGetCodeLensSymbolsForClass() throws Exception {
Preferences implementationsCodeLenses = Preferences.createFrom(Collections.singletonMap(Preferences.IMPLEMENTATIONS_CODE_LENS_ENABLED_KEY, "true"));
Preferences implementationsCodeLenses = Preferences.createFrom(Collections.singletonMap(Preferences.IMPLEMENTATIONS_CODE_LENS_KEY, "types"));
Mockito.reset(preferenceManager);
when(preferenceManager.getPreferences()).thenReturn(implementationsCodeLenses);
handler = new CodeLensHandler(preferenceManager);
Expand Down Expand Up @@ -214,7 +214,7 @@ public void testDisableCodeLensSymbols() throws Exception {

@Test
public void testEnableImplementationsCodeLensSymbols() throws Exception {
Preferences implementationsCodeLenses = Preferences.createFrom(Collections.singletonMap(Preferences.IMPLEMENTATIONS_CODE_LENS_ENABLED_KEY, "true"));
Preferences implementationsCodeLenses = Preferences.createFrom(Collections.singletonMap(Preferences.IMPLEMENTATIONS_CODE_LENS_KEY, "types"));
Mockito.reset(preferenceManager);
when(preferenceManager.getPreferences()).thenReturn(implementationsCodeLenses);
handler = new CodeLensHandler(preferenceManager);
Expand All @@ -238,7 +238,7 @@ public void testEnableImplementationsCodeLensSymbols() throws Exception {

@Test
public void testDisableImplementationsCodeLensSymbols() throws Exception {
Preferences noImplementationsCodeLenses = Preferences.createFrom(Collections.singletonMap(Preferences.IMPLEMENTATIONS_CODE_LENS_ENABLED_KEY, "false"));
Preferences noImplementationsCodeLenses = Preferences.createFrom(Collections.singletonMap(Preferences.IMPLEMENTATIONS_CODE_LENS_KEY, "types"));
Mockito.reset(preferenceManager);
when(preferenceManager.getPreferences()).thenReturn(noImplementationsCodeLenses);
Preferences noReferencesCodeLenses = Preferences.createFrom(Collections.singletonMap(Preferences.REFERENCES_CODE_LENS_ENABLED_KEY, "false"));
Expand Down Expand Up @@ -298,6 +298,86 @@ public void testResolveImplementationsCodeLens() {
assertRange(5, 13, 17, loc.getRange());
}

@SuppressWarnings("unchecked")
@Test
public void testResolveImplementationsInterfaceMethodCodeLens() {
String source = "src/java/ITest.java";
String payload = createCodeLensImplementationsRequest(source, 4, 16, 28);

CodeLens lens = getParams(payload);
Range range = lens.getRange();
assertRange(4, 16, 28, range);

CodeLens result = handler.resolve(lens, monitor);
assertNotNull(result);

// Check if command found
Command command = result.getCommand();
assertNotNull(command);
assertEquals("2 implementations", command.getTitle());
assertEquals("java.show.implementations", command.getCommand());

// Check codelens args
List<Object> args = command.getArguments();
assertEquals(3, args.size());

// Check we point to the ITest interface
String sourceUri = args.get(0).toString();
assertTrue(sourceUri.endsWith("ITest.java"));

// CodeLens position
Position p = (Position) args.get(1);
assertEquals(4, p.getLine());
assertEquals(16, p.getCharacter());

// Reference location (just checking implementation in Test.java)
List<Location> locations = (List<Location>) args.get(2);
assertEquals(2, locations.size());
Location loc = locations.stream().filter(l -> l.getUri().contains("Test")).findFirst().get();
assertTrue(loc.getUri().endsWith("src/java/Test.java"));
assertRange(5, 13, 23, loc.getRange());
}

@SuppressWarnings("unchecked")
@Test
public void testResolveImplementationsAbstractMethodCodeLens() {
String source = "src/java/Test.java";
String payload = createCodeLensImplementationsRequest(source, 7, 25, 45);

CodeLens lens = getParams(payload);
Range range = lens.getRange();
assertRange(7, 25, 45, range);

CodeLens result = handler.resolve(lens, monitor);
assertNotNull(result);

// Check if command found
Command command = result.getCommand();
assertNotNull(command);
assertEquals("1 implementation", command.getTitle());
assertEquals("java.show.implementations", command.getCommand());

// Check codelens args
List<Object> args = command.getArguments();
assertEquals(3, args.size());

// Check we point to the Test class
String sourceUri = args.get(0).toString();
assertTrue(sourceUri.endsWith("Test.java"));

// CodeLens position
Position p = (Position) args.get(1);
assertEquals(7, p.getLine());
assertEquals(25, p.getCharacter());

// Reference location (just checking implementation in Test.java)
List<Location> locations = (List<Location>) args.get(2);
assertEquals(1, locations.size());
Location loc = locations.stream().filter(l -> l.getUri().contains("Ext")).findFirst().get();
assertTrue(loc.getUri().endsWith("src/java/Ext.java"));
assertRange(8, 13, 31, loc.getRange());
}

@SuppressWarnings("unchecked")
@Test
public void testResolveCodeLense() {
Expand Down

0 comments on commit 3a6cd74

Please sign in to comment.