Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for interface and abstract method implementation code lens #3333

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -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
Loading