From 3bf568b6a2ad1db4df0d88a02b4455e0e2e82a28 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Tue, 22 Nov 2016 20:36:53 -0500 Subject: [PATCH] Hovers for application properties --- .../vscode/commons/jandex/AnnotationImpl.java | 61 +++++ .../ide/vscode/commons/jandex/FieldImpl.java | 82 ++++++ .../ide/vscode/commons/jandex/MethodImpl.java | 102 +++++++ .../ide/vscode/commons/jandex/TypeImpl.java | 132 +++++++++ .../ide/vscode/commons/jandex/Wrappers.java | 241 +---------------- .../javadoc/internal/JavadocConstants.java | 1 + .../javadoc/internal/JavadocContents.java | 30 +- .../hover/VscodeHoverEngineAdapter.java | 28 +- .../ide/vscode/commons/util/Renderables.java | 19 +- .../languageserver/testharness/Editor.java | 9 +- .../commons/project-test-harness/.gitignore | 4 + .../project/harness/ProjectsHarness.java | 2 +- .../ApplicationPropertiesLanguageServer.java | 15 +- ...opertiesCompletionProposalsCalculator.java | 256 +----------------- .../AbstractPropertyRenderableProvider.java | 127 +++++++++ .../hover/PropertiesHoverCalculator.java | 164 +++++++++++ .../hover/PropertiesHoverInfoProvider.java | 34 +++ .../hover/PropertyRenderableProvider.java | 218 +++++++++++++++ .../properties/tools/CommonLanguageTools.java | 109 ++++++++ .../test/ApplicationPropertiesEditorTest.java | 22 +- 20 files changed, 1142 insertions(+), 514 deletions(-) create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/AnnotationImpl.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/FieldImpl.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/MethodImpl.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java create mode 100644 vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/AbstractPropertyRenderableProvider.java create mode 100644 vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertiesHoverCalculator.java create mode 100644 vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertiesHoverInfoProvider.java create mode 100644 vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertyRenderableProvider.java create mode 100644 vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/tools/CommonLanguageTools.java diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/AnnotationImpl.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/AnnotationImpl.java new file mode 100644 index 000000000..8b074b2e8 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/AnnotationImpl.java @@ -0,0 +1,61 @@ +package org.springframework.ide.vscode.commons.jandex; + +import java.util.stream.Stream; + +import org.jboss.jandex.AnnotationInstance; +import org.springframework.ide.vscode.commons.java.IAnnotation; +import org.springframework.ide.vscode.commons.java.IJavadocProvider; +import org.springframework.ide.vscode.commons.java.IMemberValuePair; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + +public class AnnotationImpl implements IAnnotation { + + private AnnotationInstance annotation; + private IJavadocProvider javadocProvider; + + AnnotationImpl(AnnotationInstance annotation, IJavadocProvider javadocProvider) { + this.annotation = annotation; + this.javadocProvider = javadocProvider; + } + + @Override + public String getElementName() { + return annotation.name().toString(); + } + + @Override + public IJavadoc getJavaDoc() { + return javadocProvider == null ? null : javadocProvider.getJavadoc(this); + } + + @Override + public boolean exists() { + return true; + } + + @Override + public Stream getMemberValuePairs() { + return annotation.values().stream().map(av -> { + return Wrappers.wrap(av); + }); + } + + @Override + public String toString() { + return annotation.toString(); + } + + @Override + public int hashCode() { + return annotation.toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof AnnotationImpl) { + return annotation.toString().equals(((AnnotationImpl)obj).annotation.toString()); + } + return super.equals(obj); + } + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/FieldImpl.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/FieldImpl.java new file mode 100644 index 000000000..12fd1c086 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/FieldImpl.java @@ -0,0 +1,82 @@ +package org.springframework.ide.vscode.commons.jandex; + +import java.util.stream.Stream; + +import org.jboss.jandex.FieldInfo; +import org.springframework.ide.vscode.commons.java.Flags; +import org.springframework.ide.vscode.commons.java.IAnnotation; +import org.springframework.ide.vscode.commons.java.IField; +import org.springframework.ide.vscode.commons.java.IJavadocProvider; +import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + +class FieldImpl implements IField { + + private JandexIndex index; + private FieldInfo field; + private IJavadocProvider javadocProvider; + + FieldImpl(JandexIndex index, FieldInfo field, IJavadocProvider javadocProvider) { + this.index = index; + this.field = field; + this.javadocProvider = javadocProvider; + } + + @Override + public int getFlags() { + return field.flags(); + } + + @Override + public IType getDeclaringType() { + return Wrappers.wrap(index, field.declaringClass(), javadocProvider); + } + + @Override + public String getElementName() { + return field.name(); + } + + @Override + public IJavadoc getJavaDoc() { + return javadocProvider == null ? null : javadocProvider.getJavadoc(this); + } + + @Override + public boolean exists() { + return true; + } + + @Override + public Stream getAnnotations() { + return field.annotations().stream().map(a -> { + return Wrappers.wrap(a, javadocProvider); + }); + } + + @Override + public boolean isEnumConstant() { + return Flags.isEnum(field.flags()); + } + + @Override + public String toString() { + return field.toString(); + } + + @Override + public int hashCode() { + return field.toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof FieldImpl) { + return field.toString().equals(((FieldImpl)obj).field.toString()); + } + return super.equals(obj); + } + + + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/MethodImpl.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/MethodImpl.java new file mode 100644 index 000000000..0bc2560a2 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/MethodImpl.java @@ -0,0 +1,102 @@ +package org.springframework.ide.vscode.commons.jandex; + +import java.util.stream.Stream; + +import org.jboss.jandex.MethodInfo; +import org.springframework.ide.vscode.commons.java.IAnnotation; +import org.springframework.ide.vscode.commons.java.IJavaType; +import org.springframework.ide.vscode.commons.java.IJavadocProvider; +import org.springframework.ide.vscode.commons.java.IMethod; +import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + +public class MethodImpl implements IMethod { + + private static final String JANDEX_CONTRUCTOR_NAME = ""; + + + private JandexIndex index; + private MethodInfo method; + private IJavadocProvider javadocProvider; + + MethodImpl(JandexIndex index, MethodInfo method, IJavadocProvider javadocProvider) { + this.index = index; + this.method = method; + this.javadocProvider =javadocProvider; + } + + @Override + public int getFlags() { + return method.flags(); + } + + @Override + public boolean isConstructor() { + return method.name().equals(JANDEX_CONTRUCTOR_NAME); + } + + @Override + public IType getDeclaringType() { + return Wrappers.wrap(index, method.declaringClass(), javadocProvider); + } + + @Override + public String getElementName() { + return isConstructor() ? getDeclaringType().getElementName() : method.name(); + } + + @Override + public IJavadoc getJavaDoc() { + return javadocProvider == null ? null : javadocProvider.getJavadoc(this); + } + + @Override + public boolean exists() { + return true; + } + + @Override + public Stream getAnnotations() { + return method.annotations().stream().map(a -> Wrappers.wrap(a, javadocProvider)); + } + + @Override + public IJavaType getReturnType() { + return Wrappers.wrap(method.returnType()); + } + +// @Override +// public String getSignature() { +// StringBuilder sb = new StringBuilder(); +// sb.append('('); +// method.parameters().forEach(p -> sb.append(signature(p))); +// sb.append(')'); +// sb.append(getReturnType()); +// return sb.toString(); +// } + + @Override + public String toString() { + return method.toString(); + } + + @Override + public Stream parameters() { + return method.parameters().stream().map(Wrappers::wrap); + } + + @Override + public int hashCode() { + return method.toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof MethodImpl) { + return method.toString().equals(((MethodImpl)obj).method.toString()); + } + return super.equals(obj); + } + + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java new file mode 100644 index 000000000..c20051a05 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java @@ -0,0 +1,132 @@ +package org.springframework.ide.vscode.commons.jandex; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; +import org.springframework.ide.vscode.commons.java.Flags; +import org.springframework.ide.vscode.commons.java.IAnnotation; +import org.springframework.ide.vscode.commons.java.IField; +import org.springframework.ide.vscode.commons.java.IJavaType; +import org.springframework.ide.vscode.commons.java.IJavadocProvider; +import org.springframework.ide.vscode.commons.java.IMethod; +import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + +class TypeImpl implements IType { + + private ClassInfo info; + private JandexIndex index; + private IJavadocProvider javadocProvider; + + TypeImpl(JandexIndex index, ClassInfo info, IJavadocProvider javadocProvider) { + this.info = info; + this.index = index; + this.javadocProvider = javadocProvider; + } + + @Override + public int getFlags() { + return info.flags(); + } + + @Override + public IType getDeclaringType() { + DotName enclosingClass = info.enclosingClass(); + return enclosingClass == null ? null : index.getClassByName(enclosingClass); + } + + @Override + public String getElementName() { + return info.simpleName() == null ? info.name().local() : info.simpleName(); + } + + @Override + public IJavadoc getJavaDoc() { + return javadocProvider == null ? null : javadocProvider.getJavadoc(this); + } + + @Override + public boolean exists() { + return true; + } + + @Override + public Stream getAnnotations() { + // TODO: check correctness! + return info.annotations().get(info.name()).stream().map(a -> Wrappers.wrap(a, javadocProvider)); + } + + @Override + public boolean isClass() { + return true; + } + + @Override + public boolean isEnum() { + return Flags.isEnum(info.flags()); + } + + @Override + public boolean isInterface() { + return Flags.isInterface(info.flags()); + } + + @Override + public boolean isAnnotation() { + return Flags.isAnnotation(info.flags()); + } + + @Override + public String getFullyQualifiedName() { + return info.name().toString(); + } + + @Override + public IField getField(String name) { + return Wrappers.wrap(index, info.field(name), javadocProvider); + } + + @Override + public Stream getFields() { + return info.fields().stream().map(f -> { + return Wrappers.wrap(index, f, javadocProvider); + }); + } + + @Override + public IMethod getMethod(String name, Stream parameters) { + List typeParameters = parameters.map(Wrappers::from).collect(Collectors.toList()); + return Wrappers.wrap(index, info.method(name, typeParameters.toArray(new Type[typeParameters.size()])), javadocProvider); + } + + @Override + public Stream getMethods() { + return info.methods().stream().map(m -> { + return Wrappers.wrap(index, m, javadocProvider); + }); + } + + @Override + public String toString() { + return info.toString(); + } + + @Override + public int hashCode() { + return info.toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TypeImpl) { + return info.toString().equals(((TypeImpl)obj).info.toString()); + } + return super.equals(obj); + } + + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java index 7b78b36a0..f20d1eb9b 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java @@ -2,20 +2,14 @@ package org.springframework.ide.vscode.commons.jandex; import static org.springframework.ide.vscode.commons.util.Assert.isNotNull; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.PrimitiveType; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; -import org.springframework.ide.vscode.commons.java.Flags; import org.springframework.ide.vscode.commons.java.IAnnotation; import org.springframework.ide.vscode.commons.java.IField; import org.springframework.ide.vscode.commons.java.IJavaType; @@ -25,257 +19,32 @@ import org.springframework.ide.vscode.commons.java.IMethod; import org.springframework.ide.vscode.commons.java.IPrimitiveType; import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.java.IVoidType; -import org.springframework.ide.vscode.commons.javadoc.IJavadoc; public class Wrappers { - private static final String JANDEX_CONTRUCTOR_NAME = ""; - public static IType wrap(JandexIndex index, ClassInfo info, IJavadocProvider javadocProvider) { if (info == null) { return null; } - return new IType() { - - @Override - public int getFlags() { - return info.flags(); - } - - @Override - public IType getDeclaringType() { - DotName enclosingClass = info.enclosingClass(); - return enclosingClass == null ? null : index.getClassByName(enclosingClass); - } - - @Override - public String getElementName() { - return info.simpleName() == null ? info.name().local() : info.simpleName(); - } - - @Override - public IJavadoc getJavaDoc() { - return javadocProvider == null ? null : javadocProvider.getJavadoc(this); - } - - @Override - public boolean exists() { - return true; - } - - @Override - public Stream getAnnotations() { - // TODO: check correctness! - return info.annotations().get(info.name()).stream().map(a -> wrap(a, javadocProvider)); - } - - @Override - public boolean isClass() { - return true; - } - - @Override - public boolean isEnum() { - return Flags.isEnum(info.flags()); - } - - @Override - public boolean isInterface() { - return Flags.isInterface(info.flags()); - } - - @Override - public boolean isAnnotation() { - return Flags.isAnnotation(info.flags()); - } - - @Override - public String getFullyQualifiedName() { - return info.name().toString(); - } - - @Override - public IField getField(String name) { - return wrap(index, info.field(name), javadocProvider); - } - - @Override - public Stream getFields() { - return info.fields().stream().map(f -> { - return wrap(index, f, javadocProvider); - }); - } - - @Override - public IMethod getMethod(String name, Stream parameters) { - List typeParameters = parameters.map(Wrappers::from).collect(Collectors.toList()); - return wrap(index, info.method(name, typeParameters.toArray(new Type[typeParameters.size()])), javadocProvider); - } - - @Override - public Stream getMethods() { - return info.methods().stream().map(m -> { - return wrap(index, m, javadocProvider); - }); - } - - @Override - public String toString() { - return info.toString(); - } - - }; + return new TypeImpl(index, info, javadocProvider); } public static IField wrap(JandexIndex index, FieldInfo field, IJavadocProvider javadocProvider) { if (field == null) { return null; } - return new IField() { - - @Override - public int getFlags() { - return field.flags(); - } - - @Override - public IType getDeclaringType() { - return wrap(index, field.declaringClass(), javadocProvider); - } - - @Override - public String getElementName() { - return field.name(); - } - - @Override - public IJavadoc getJavaDoc() { - return javadocProvider == null ? null : javadocProvider.getJavadoc(this); - } - - @Override - public boolean exists() { - return true; - } - - @Override - public Stream getAnnotations() { - return field.annotations().stream().map(a -> { - return wrap(a, javadocProvider); - }); - } - - @Override - public boolean isEnumConstant() { - return Flags.isEnum(field.flags()); - } - - @Override - public String toString() { - return field.toString(); - } - }; + return new FieldImpl(index, field, javadocProvider); } public static IMethod wrap(JandexIndex index, MethodInfo method, IJavadocProvider javadocProvider) { isNotNull(index); isNotNull(method); - return new IMethod() { - - @Override - public int getFlags() { - return method.flags(); - } - - @Override - public boolean isConstructor() { - return method.name().equals(JANDEX_CONTRUCTOR_NAME); - } - - @Override - public IType getDeclaringType() { - return wrap(index, method.declaringClass(), javadocProvider); - } - - @Override - public String getElementName() { - return isConstructor() ? getDeclaringType().getElementName() : method.name(); - } - - @Override - public IJavadoc getJavaDoc() { - return javadocProvider == null ? null : javadocProvider.getJavadoc(this); - } - - @Override - public boolean exists() { - return true; - } - - @Override - public Stream getAnnotations() { - return method.annotations().stream().map(a -> wrap(a, javadocProvider)); - } - - @Override - public IJavaType getReturnType() { - return wrap(method.returnType()); - } - -// @Override -// public String getSignature() { -// StringBuilder sb = new StringBuilder(); -// sb.append('('); -// method.parameters().forEach(p -> sb.append(signature(p))); -// sb.append(')'); -// sb.append(getReturnType()); -// return sb.toString(); -// } - - @Override - public String toString() { - return method.toString(); - } - - @Override - public Stream parameters() { - return method.parameters().stream().map(Wrappers::wrap); - } - - }; + return new MethodImpl(index, method, javadocProvider); } public static IAnnotation wrap(AnnotationInstance annotation, IJavadocProvider javadocProvider) { isNotNull(annotation); - return new IAnnotation() { - - @Override - public String getElementName() { - return annotation.name().toString(); - } - - @Override - public IJavadoc getJavaDoc() { - return javadocProvider == null ? null : javadocProvider.getJavadoc(this); - } - - @Override - public boolean exists() { - return true; - } - - @Override - public Stream getMemberValuePairs() { - return annotation.values().stream().map(av -> { - return wrap(av); - }); - } - - @Override - public String toString() { - return annotation.toString(); - } - }; + return new AnnotationImpl(annotation, javadocProvider); } public static IMemberValuePair wrap(AnnotationValue annotationValue) { @@ -324,7 +93,7 @@ public class Wrappers { } @SuppressWarnings("unchecked") - private static Type from(IJavaType type) { + static Type from(IJavaType type) { if (type == IPrimitiveType.BOOLEAN) { return PrimitiveType.BOOLEAN; } else if (type == IPrimitiveType.BYTE) { diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java index 01ca87833..90ff08b3b 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java @@ -12,6 +12,7 @@ public interface JavadocConstants { char[] FIELD_DETAIL= "".toCharArray(); //$NON-NLS-1$ char[] FIELD_SUMMARY = "".toCharArray(); //$NON-NLS-1$ char[] ENUM_CONSTANT_SUMMARY = "".toCharArray(); //$NON-NLS-1$ + char[] ENUM_CONSTANT_DETAIL = "".toCharArray(); char[] ANNOTATION_TYPE_REQUIRED_MEMBER_SUMMARY = "".toCharArray(); //$NON-NLS-1$ char[] ANNOTATION_TYPE_OPTIONAL_MEMBER_SUMMARY = "".toCharArray(); //$NON-NLS-1$ char[] END_OF_CLASS_DATA = "".toCharArray(); //$NON-NLS-1$ diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java index 80296589e..0e7584933 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java @@ -17,11 +17,13 @@ public class JavadocContents { private boolean hasComputedChildrenSections = false; private int indexOfFieldDetails; + private int indexOfEnumConstantsDetails; private int indexOfConstructorDetails; private int indexOfMethodDetails; private int indexOfEndOfClassData; private int indexOfFieldsBottom; + private int indexOfEnumConstantsBottom; private int indexOfAllMethodsTop; private int indexOfAllMethodsBottom; @@ -225,10 +227,14 @@ public class JavadocContents { int lastIndex = CharOperation.indexOf(JavadocConstants.SEPARATOR_START, this.content, false, this.childrenStart); lastIndex = lastIndex == -1 ? this.childrenStart : lastIndex; + // try to find enum cosntants detail start + this.indexOfEnumConstantsDetails = CharOperation.indexOf(JavadocConstants.ENUM_CONSTANT_DETAIL, this.content, false, lastIndex); + lastIndex = this.indexOfEnumConstantsDetails == -1 ? lastIndex : this.indexOfEnumConstantsDetails; + // try to find field detail start this.indexOfFieldDetails = CharOperation.indexOf(JavadocConstants.FIELD_DETAIL, this.content, false, lastIndex); lastIndex = this.indexOfFieldDetails == -1 ? lastIndex : this.indexOfFieldDetails; - + // try to find constructor detail start this.indexOfConstructorDetails = CharOperation.indexOf(JavadocConstants.CONSTRUCTOR_DETAIL, this.content, false, lastIndex); lastIndex = this.indexOfConstructorDetails == -1 ? lastIndex : this.indexOfConstructorDetails; @@ -243,6 +249,16 @@ public class JavadocContents { int[] classDataRange = sanitizeRange(new int[] { indexOfStartOfClassData + JavadocConstants.START_OF_CLASS_DATA.length, indexOfEndOfClassData}, "ul", "li", "div"); this.indexOfEndOfClassData = classDataRange[1]; + // try to find enum constants bottom + this.indexOfEnumConstantsBottom = this.indexOfFieldDetails != -1 ? this.indexOfFieldDetails + : this.indexOfConstructorDetails != -1 ? this.indexOfConstructorDetails + : this.indexOfMethodDetails != -1 ? this.indexOfMethodDetails : this.indexOfEndOfClassData; + + // Get rid of possible
  • tag wrappers + int[] fieldsRange = sanitizeRange(new int[] {indexOfEnumConstantsDetails + JavadocConstants.ENUM_CONSTANT_DETAIL.length, indexOfEnumConstantsBottom}, "ul", "li", "div"); + indexOfEnumConstantsDetails = fieldsRange[0]; + indexOfEnumConstantsBottom = fieldsRange[1]; + // try to find the field detail end this.indexOfFieldsBottom = this.indexOfConstructorDetails != -1 ? this.indexOfConstructorDetails : @@ -257,7 +273,7 @@ public class JavadocContents { this.indexOfAllMethodsBottom = this.indexOfEndOfClassData; // Get rid of possible
    • tag wrappers - int[] fieldsRange = sanitizeRange(new int[] {indexOfFieldDetails + JavadocConstants.FIELD_DETAIL.length, indexOfFieldsBottom}, "ul", "li", "div"); + fieldsRange = sanitizeRange(new int[] {indexOfFieldDetails + JavadocConstants.FIELD_DETAIL.length, indexOfFieldsBottom}, "ul", "li", "div"); indexOfFieldDetails = fieldsRange[0]; indexOfFieldsBottom = fieldsRange[1]; @@ -303,8 +319,10 @@ public class JavadocContents { char[] anchor = String.valueOf(buffer).toCharArray(); int[] range = null; + int top = field.isEnumConstant() ? this.indexOfEnumConstantsDetails : this.indexOfFieldDetails; + int bottom = field.isEnumConstant() ? this.indexOfEnumConstantsBottom : this.indexOfFieldsBottom; - if (this.indexOfFieldDetails == -1 || this.indexOfFieldsBottom == -1) { + if (top == -1 || bottom == -1) { // the detail section has no top or bottom, so the doc has an unknown format if (this.unknownFormatAnchorIndexes == null) { this.unknownFormatAnchorIndexes = new int[(int)type.getFields().count()]; @@ -316,7 +334,7 @@ public class JavadocContents { this.tempAnchorIndexesCount = this.unknownFormatAnchorIndexesCount; this.tempLastAnchorFoundIndex = this.unknownFormatLastAnchorFoundIndex; - range = computeChildRange(anchor, this.indexOfFieldsBottom); + range = computeChildRange(anchor, bottom); this.unknownFormatLastAnchorFoundIndex = this.tempLastAnchorFoundIndex; this.unknownFormatAnchorIndexesCount = this.tempAnchorIndexesCount; @@ -325,14 +343,14 @@ public class JavadocContents { if (this.fieldAnchorIndexes == null) { this.fieldAnchorIndexes = new int[(int)type.getFields().count()]; this.fieldAnchorIndexesCount = 0; - this.fieldLastAnchorFoundIndex = this.indexOfFieldDetails; + this.fieldLastAnchorFoundIndex = top; } this.tempAnchorIndexes = this.fieldAnchorIndexes; this.tempAnchorIndexesCount = this.fieldAnchorIndexesCount; this.tempLastAnchorFoundIndex = this.fieldLastAnchorFoundIndex; - range = computeChildRange(anchor, this.indexOfFieldsBottom); + range = computeChildRange(anchor, bottom); this.fieldLastAnchorFoundIndex = this.tempLastAnchorFoundIndex; this.fieldAnchorIndexesCount = this.tempAnchorIndexesCount; diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java index 6a12177f4..cff44c2aa 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java @@ -29,14 +29,29 @@ import reactor.util.function.Tuple2; public class VscodeHoverEngineAdapter implements VscodeHoverEngine { + public enum HoverType { + MARKDOWN, + HTML + } + private HoverInfoProvider hoverInfoProvider; private SimpleLanguageServer server; + private HoverType type; final static Logger logger = LoggerFactory.getLogger(VscodeHoverEngineAdapter.class); public VscodeHoverEngineAdapter(SimpleLanguageServer server, HoverInfoProvider hoverInfoProvider) { + this(server, hoverInfoProvider, HoverType.MARKDOWN); + } + + public VscodeHoverEngineAdapter(SimpleLanguageServer server, HoverInfoProvider hoverInfoProvider, HoverType type) { this.hoverInfoProvider = hoverInfoProvider; this.server = server; + this.type = type; + } + + public void setHoverType(HoverType type) { + this.type = type; } @Override @@ -56,7 +71,7 @@ public class VscodeHoverEngineAdapter implements VscodeHoverEngine { IRegion region = hoverTuple.getT2(); Range range = doc.toRange(region.getOffset(), region.getLength()); - Hover hover = new Hover(Collections.singletonList(hoverInfo.toMarkdown()), range); + Hover hover = new Hover(Collections.singletonList(render(hoverInfo, type)), range); return Futures.of(hover); } @@ -67,4 +82,15 @@ public class VscodeHoverEngineAdapter implements VscodeHoverEngine { return SimpleTextDocumentService.NO_HOVER; } + private static String render(Renderable renderable, HoverType type) { + switch (type) { + case HTML: + return renderable.toHtml(); + case MARKDOWN: + return renderable.toMarkdown(); + default: + return renderable.toMarkdown(); + } + } + } diff --git a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderables.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderables.java index f34435c18..389917dbc 100644 --- a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderables.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderables.java @@ -34,25 +34,34 @@ public class Renderables { public static final Renderable NO_DESCRIPTION = italic(text(NO_DESCRIPTION_TEXT)); - private static Remark getHtmlToMarkdownConverter() { + public static Remark getHtmlToMarkdownConverter() { return new Remark(); } - - public static Renderable htmlBlob(String html) { + + @FunctionalInterface + public interface HtmlContentFiller { + void fill(HtmlBuffer buffer); + } + + public static Renderable htmlBlob(HtmlContentFiller contentFiller) { return new Renderable() { @Override public void renderAsHtml(HtmlBuffer buffer) { - buffer.raw(html); + contentFiller.fill(buffer); } @Override public void renderAsMarkdown(StringBuilder buffer) { - buffer.append(getHtmlToMarkdownConverter().convert(html)); + buffer.append(getHtmlToMarkdownConverter().convert(toHtml())); } }; } + + public static Renderable htmlBlob(String html) { + return htmlBlob(buffer -> buffer.raw(html)); + } public static Renderable concat(Renderable... pieces) { return concat(ImmutableList.copyOf(pieces)); diff --git a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index e8f3abe72..0b7b45e2a 100644 --- a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -329,8 +329,13 @@ public class Editor { * computed when hovering mouse at position at the end of first occurrence of * a given string in the editor. */ - public void assertHoverText(String afterString, String expectSnippet) { - throw new UnsupportedOperationException("Not implemented yet!"); + public void assertHoverText(String afterString, String expectSnippet) throws Exception { + int pos = getRawText().indexOf(afterString); + if (pos>=0) { + pos += afterString.length(); + } + Hover hover = harness.getHover(document, document.toPosition(pos)); + assertContains(expectSnippet, hover.getContents().toString()); } public void setSelection(int start, int end) { diff --git a/vscode-extensions/commons/project-test-harness/.gitignore b/vscode-extensions/commons/project-test-harness/.gitignore index 145500786..4fd319e42 100644 --- a/vscode-extensions/commons/project-test-harness/.gitignore +++ b/vscode-extensions/commons/project-test-harness/.gitignore @@ -1,2 +1,6 @@ **/classpath.txt **/bin/** +**/*.log.* +test-projects/**/.classpath +test-projects/**/.project +test-projects/**/.factorypath \ No newline at end of file diff --git a/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java b/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java index ac5b6d03d..3aba14957 100644 --- a/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java +++ b/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java @@ -60,7 +60,7 @@ public class ProjectsHarness { Path testProjectPath = getProjectPath(name); switch (type) { case MAVEN: - MavenBuilder.newBuilder(testProjectPath).clean().pack()./*javadoc().*/skipTests().execute(); + MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute(); return new MavenJavaProject(testProjectPath.resolve(MavenCore.POM_XML).toFile()); case CLASSPATH_TXT: MavenBuilder.newBuilder(testProjectPath).clean().pack().skipTests().execute(); diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java index 8fa92b741..712922b87 100644 --- a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java @@ -14,10 +14,13 @@ import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.springframework.ide.vscode.application.properties.completions.SpringPropertiesCompletionEngine; +import org.springframework.ide.vscode.application.properties.hover.PropertiesHoverInfoProvider; import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider; import org.springframework.ide.vscode.application.properties.reconcile.SpringPropertiesReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; +import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter; +import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter.HoverType; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; @@ -35,6 +38,7 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer { private TypeUtilProvider typeUtilProvider; private VscodeCompletionEngineAdapter completionEngine; private SpringPropertiesReconcileEngine reconcileEngine; + private VscodeHoverEngineAdapter hoverEngine; public ApplicationPropertiesLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder javaProjectFinder) { @@ -57,7 +61,10 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer { completionEngine.setMaxCompletionsNumber(40); documents.onCompletion(completionEngine::getCompletions); documents.onCompletionResolve(completionEngine::resolveCompletion); - + + PropertiesHoverInfoProvider hoverInfoProvider = new PropertiesHoverInfoProvider(indexProvider, typeUtilProvider, javaProjectFinder); + hoverEngine = new VscodeHoverEngineAdapter(this, hoverInfoProvider); + documents.onHover(hoverEngine::getHover); } public void setMaxCompletionsNumber(int number) { @@ -68,6 +75,10 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer { reconcileEngine.setRecordSyntaxErrors(record); } + public void setHoverType(HoverType type) { + hoverEngine.setHoverType(type); + } + @Override protected ServerCapabilities getServerCapabilities() { ServerCapabilities c = new ServerCapabilities(); @@ -78,6 +89,8 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer { completionProvider.setResolveProvider(false); c.setCompletionProvider(completionProvider); + c.setHoverProvider(true); + return c; } diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/PropertiesCompletionProposalsCalculator.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/PropertiesCompletionProposalsCalculator.java index c267e465e..536e85cda 100644 --- a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/PropertiesCompletionProposalsCalculator.java +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/PropertiesCompletionProposalsCalculator.java @@ -1,12 +1,12 @@ package org.springframework.ide.vscode.application.properties.completions; import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens; +import static org.springframework.ide.vscode.application.properties.tools.CommonLanguageTools.*; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.regex.Pattern; import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo; import org.springframework.ide.vscode.application.properties.metadata.completions.PropertyCompletionFactory; @@ -30,7 +30,6 @@ import org.springframework.ide.vscode.commons.languageserver.util.BadLocationExc import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.languageserver.util.IDocument; import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder; -import org.springframework.ide.vscode.commons.languageserver.util.TextDocument; import org.springframework.ide.vscode.commons.util.CollectionUtil; import org.springframework.ide.vscode.commons.util.FuzzyMatcher; import org.springframework.ide.vscode.commons.util.Log; @@ -43,16 +42,8 @@ import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value import com.google.common.collect.ImmutableList; -class PropertiesCompletionProposalsCalculator { - - private static final Pattern SPACES = Pattern.compile( - "(\\s|\\\\\\s)*" - ); - - private static boolean isValuePrefixChar(char c) { - return !Character.isWhitespace(c) && c!=','; - } - +public class PropertiesCompletionProposalsCalculator { + private static final PrefixFinder valuePrefixFinder = new PrefixFinder() { protected boolean isPrefixChar(char c) { return isValuePrefixChar(c); @@ -103,7 +94,7 @@ class PropertiesCompletionProposalsCalculator { private boolean preferLowerCaseEnums; private AntlrParser parser; - PropertiesCompletionProposalsCalculator(FuzzyMap index, TypeUtil typeUtil, PropertyCompletionFactory completionFactory, IDocument doc, int offset, boolean preferLowerCaseEnums) { + public PropertiesCompletionProposalsCalculator(FuzzyMap index, TypeUtil typeUtil, PropertyCompletionFactory completionFactory, IDocument doc, int offset, boolean preferLowerCaseEnums) { this.index = index; this.typeUtil = typeUtil; this.completionFactory = completionFactory; @@ -260,76 +251,6 @@ class PropertiesCompletionProposalsCalculator { return postfix; } -// public static boolean isAssign(char assign) { -// return assign==':'||assign=='='; -// } -// -// private KeyValuePair getAstNodeLine(IDocument doc, int offset) { -// List pairs = parser.parse(doc.get()).ast.getNodes(KeyValuePair.class); -// return findPair(pairs, offset, 0, pairs.size() - 1); -// } -// -// private KeyValuePair findPair(List pairs, int offset, int start, int end) { -// if (start == end) { -// KeyValuePair pair = pairs.get(start); -// if (pair.getOffset() <= offset && offset <= pair.getOffset() + pair.getLength()) { -// return pair; -// } else { -// return null; -// } -// } else if (start < end ) { -// int pivotIndex = (start + end) / 2; -// KeyValuePair pair = pairs.get(pivotIndex); -// if (pair.getOffset() > offset) { -// return findPair(pairs, offset, start, pivotIndex - 1); -// } else if (offset > pair.getOffset() + pair.getLength()) { -// return findPair(pairs, offset, pivotIndex + 1, end); -// } else { -// return pair; -// } -// } else { -// return null; -// } -// } - -// private HoverInfo getValueHoverInfo(DocumentRegion value) { -// try { -// String valueString = value.toString(); -// IDocument doc = value.getDocument(); -// ITypedRegion valuePartition = getPartition(value.getDocument(), value.getStart()); -// int valuePartitionStart = valuePartition.getOffset(); -// String propertyName = fuzzySearchPrefix.getPrefix(doc, valuePartitionStart); //note: no need to skip whitespace backwards. -// //because value partition includes whitespace around the assignment -// -// Type type = getValueType(propertyName); -// if (TypeUtil.isArray(type) || TypeUtil.isList(type)) { -// //It is useful to provide content assist for the values in the list when entering a list -// type = TypeUtil.getDomainType(type); -// } -// if (TypeUtil.isClass(type)) { -// //Special case. We want to provide hoverinfos more liberally than what's suggested for completions (i.e. even class names -// //that are not suggested by the hints because they do not meet subtyping constraints should be hoverable and linkable! -// StsValueHint hint = StsValueHint.className(valueString, typeUtil); -// if (hint!=null) { -// return new ValueHintHoverInfo(hint); -// } -// } -// //Hack: pretend to invoke content-assist at the end of the value text. This should provide hints applicable to that value -// // then show hoverinfo based on that. That way we can avoid duplication a lot of similar logic to compute hoverinfos and hyperlinks. -// Collection hints = getValueHints(valueString, propertyName, EnumCaseMode.ALIASED); -// if (hints!=null) { -// for (StsValueHint h : hints) { -// if (valueString.equals(h.getValue())) { -// return new ValueHintHoverInfo(h); -// } -// } -// } -// } catch (BadLocationException e) { -// Log.log(e); -// } -// return null; -// } - private Collection getValueCompletions(Value value) { DocumentRegion valueRegion = createRegion(doc, value).trimStart(SPACES).trimEnd(SPACES); String query = valuePrefixFinder.getPrefix(doc, offset, valueRegion.getStart()); @@ -340,7 +261,7 @@ class PropertiesCompletionProposalsCalculator { String propertyName = /*fuzzySearchPrefix.getPrefix(doc, pair.getOffset())*/value.getParent().getKey().decode(); // because value partition includes whitespace around the assignment if (propertyName != null) { - Collection valueCompletions = getValueHints(query, propertyName, caseMode); + Collection valueCompletions = getValueHints(index, typeUtil, query, propertyName, caseMode); if (valueCompletions != null && !valueCompletions.isEmpty()) { ArrayList proposals = new ArrayList(); for (StsValueHint hint : valueCompletions) { @@ -350,7 +271,7 @@ class PropertiesCompletionProposalsCalculator { DocumentEdits edits = new DocumentEdits(doc); edits.delete(startOfValue, offset); edits.insert(offset, valueCandidate); - proposals.add(completionFactory.valueProposal(valueCandidate, query, getValueType(propertyName), + proposals.add(completionFactory.valueProposal(valueCandidate, query, getValueType(index, typeUtil, propertyName), score, edits, new ValueHintHoverInfo(hint)) // new ValueProposal(startOfValue, valuePrefix, // valueCandidate, i) @@ -374,54 +295,6 @@ class PropertiesCompletionProposalsCalculator { return new DocumentRegion(doc, value.getOffset(), value.getOffset() + length); } - private Collection getValueHints(String query, String propertyName, EnumCaseMode caseMode) { - Type type = getValueType(propertyName); - if (TypeUtil.isArray(type) || TypeUtil.isList(type)) { - //It is useful to provide content assist for the values in the list when entering a list - type = TypeUtil.getDomainType(type); - } - List allHints = new ArrayList<>(); - { - Collection hints = typeUtil.getHintValues(type, query, caseMode); - if (CollectionUtil.hasElements(hints)) { - allHints.addAll(hints); - } - } - { - PropertyInfo prop = index.findLongestCommonPrefixEntry(propertyName); - if (prop!=null) { - HintProvider hintProvider = prop.getHints(typeUtil, false); - if (!HintProviders.isNull(hintProvider)) { - allHints.addAll(hintProvider.getValueHints(query)); - } - } - } - return allHints; - } - - /** - * Determine the value type for a give propertyName. - */ - protected Type getValueType(String propertyName) { - try { - PropertyInfo prop = index.get(propertyName); - if (prop!=null) { - return TypeParser.parse(prop.getType()); - } else { - prop = findLongestValidProperty(index, propertyName); - if (prop!=null) { - TextDocument doc = new TextDocument(null); - doc.setText(propertyName); - PropertyNavigator navigator = new PropertyNavigator(doc, null, typeUtil, new DocumentRegion(doc, 0, doc.getLength())); - return navigator.navigate(prop.getId().length(), TypeParser.parse(prop.getType())); - } - } - } catch (Exception e) { - Log.log(e); - } - return null; - } - private List> findMatches(String prefix) { List> matches = index.find(camelCaseToHyphens(prefix)); return matches; @@ -462,121 +335,4 @@ class PropertiesCompletionProposalsCalculator { return Collections.emptyList(); } -// public HoverInfo getHoverInfo(IDocument doc, IRegion _region) { -// debug("getHoverInfo("+_region+")"); -// -// //The delegate 'getHoverRegion' for spring propery editor will return smaller word regions. -// // we must ensure to use our own region finder to identify correct property name. -// ITypedRegion region = getHoverRegion(doc, _region.getOffset()); -// if (region!=null) { -// String contentType = region.getType(); -// try { -// if (contentType.equals(IDocument.DEFAULT_CONTENT_TYPE)) { -// debug("hoverRegion = "+region); -// PropertyInfo best = findBestHoverMatch(doc.get(region.getOffset(), region.getLength()).trim()); -// if (best!=null) { -// return new SpringPropertyHoverInfo(documentContextFinder.getJavaProject(doc), best); -// } -// } else if (contentType.equals(IPropertiesFilePartitions.PROPERTY_VALUE)) { -// return getValueHoverInfo(new DocumentRegion(doc, region)); -// } -// } catch (Exception e) { -// SpringPropertiesEditorPlugin.log(e); -// } -// } -// return null; -// } -// -// public ITypedRegion getHoverRegion(IDocument document, int offset) { -// try { -// ITypedRegion candidate = getPartition(document, offset); -// if (candidate!=null) { -// String type = candidate.getType(); -// if (IDocument.DEFAULT_CONTENT_TYPE.equals(type)) { -// return candidate; -// } else if (IPropertiesFilePartitions.PROPERTY_VALUE.equals(type)) { -// DocumentRegion valueRegion = new DocumentRegion(document, candidate).trimStart(ASSIGN); -// return getValueHoverRegion(valueRegion, valueRegion.toRelative(offset)); -// } -// } -// } catch (Exception e) { -// SpringPropertiesEditorPlugin.log(e); -// } -// return null; -// } -// -// private ITypedRegion getValueHoverRegion(DocumentRegion r, int offset) { -// int len = r.length(); -// if (offset>=0 && offset<=len) { -// int start = offset; -// while (start>0 && isValuePrefixChar(r.charAt(start-1))) { -// start--; -// } -// int end = offset; -// while (endbestCommonPrefixLen // it has a longer common prefix -// || commonPrefixLen==bestCommonPrefixLen && extraLen index, String name) { - int bracketPos = name.indexOf('['); - int endPos = bracketPos>=0?bracketPos:name.length(); - PropertyInfo prop = null; - String prefix = null; - while (endPos>0 && prop==null) { - prefix = name.substring(0, endPos); - String canonicalPrefix = camelCaseToHyphens(prefix); - prop = index.get(canonicalPrefix); - if (prop==null) { - endPos = name.lastIndexOf('.', endPos-1); - } - } - if (prop!=null) { - //We should meet caller's expectation that matched properties returned by this method - // match the names exactly even if we found them using relaxed name matching. - return prop.withId(prefix); - } - return null; - } - - } diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/AbstractPropertyRenderableProvider.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/AbstractPropertyRenderableProvider.java new file mode 100644 index 000000000..bbf5e74c3 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/AbstractPropertyRenderableProvider.java @@ -0,0 +1,127 @@ +package org.springframework.ide.vscode.application.properties.hover; + +import java.util.Collection; + +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.HtmlBuffer; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.util.StringUtil; + +public abstract class AbstractPropertyRenderableProvider { + + /** + * Fake host name that is used in 'action link' urls so that we can + * recognize them as such. + */ + private static final String ACTION_HOST = "action"; + + Renderable getRenderable() { + return new Renderable() { + + @Override + public void renderAsHtml(HtmlBuffer buffer) { +// JavaTypeLinks jtLinks = new JavaTypeLinks(this); + + renderId(buffer); + + String type = getType(); + if (type==null) { + type = Object.class.getName(); + } +// jtLinks.javaTypeLink(html, getJavaProject(), type); + actionLink(buffer, type); + + String deflt = formatDefaultValue(getDefaultValue()); + if (deflt!=null) { + buffer.raw("

      "); + buffer.text("Default: "); + buffer.raw(""); + buffer.text(deflt); + buffer.raw(""); + } + + if (isDeprecated()) { + buffer.raw("

      "); + String reason = getDeprecationReason(); + if (StringUtil.hasText(reason)) { + buffer.bold("Deprecated: "); + buffer.text(reason); + } else { + buffer.bold("Deprecated!"); + } + } + + Renderable description = getDescription(); + if (description!=null) { + buffer.raw("

      "); + buffer.p(description.toHtml()); + } + + } + + @Override + public void renderAsMarkdown(StringBuilder buffer) { + buffer.append(Renderables.getHtmlToMarkdownConverter().convert(toHtml())); + } + + }; + } + + final protected void renderId(HtmlBuffer html) { + boolean deprecated = isDeprecated(); + String tag = deprecated ? "s" : "b"; + String replacement = getDeprecationReplacement(); + + html.raw("<"+tag+">"); + html.text(getId()); + html.raw(""); + if (StringUtil.hasText(replacement)) { + html.text(" -> "+ replacement); + } + html.raw("
      "); + } + + protected abstract Object getDefaultValue(); + protected abstract IJavaProject getJavaProject(); + protected abstract Renderable getDescription(); + protected abstract String getType(); + protected abstract String getDeprecationReason(); + protected abstract String getId(); + protected abstract String getDeprecationReplacement(); + protected abstract boolean isDeprecated(); + + public static String formatDefaultValue(Object defaultValue) { + if (defaultValue!=null) { + if (defaultValue instanceof String) { + return (String) defaultValue; + } else if (defaultValue instanceof Number) { + return ((Number)defaultValue).toString(); + } else if (defaultValue instanceof Boolean) { + return Boolean.toString((Boolean) defaultValue); + } else if (defaultValue instanceof Object[]) { + return StringUtil.arrayToCommaDelimitedString((Object[]) defaultValue); + } else if (defaultValue instanceof Collection) { + return StringUtil.collectionToCommaDelimitedString((Collection) defaultValue); + } else { + //no idea what it is but try 'toString' and hope for the best + return defaultValue.toString(); + } + } + return null; + } + + /** + * Creates an 'action' link and adds it to the html buffer. When the user clicks the given + * link then the provided runnable is to be executed. + */ + public void actionLink(HtmlBuffer html, String displayString/*, Runnable runnable*/) { +// String actionId = registerAction(runnable); + html.raw(""); + html.text(displayString); + html.raw(""); + } + +} diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertiesHoverCalculator.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertiesHoverCalculator.java new file mode 100644 index 000000000..13625d48a --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertiesHoverCalculator.java @@ -0,0 +1,164 @@ +package org.springframework.ide.vscode.application.properties.hover; + +import static org.springframework.ide.vscode.application.properties.tools.CommonLanguageTools.SPACES; +import static org.springframework.ide.vscode.application.properties.tools.CommonLanguageTools.getValueHints; +import static org.springframework.ide.vscode.application.properties.tools.CommonLanguageTools.getValueType; + +import java.util.Collection; +import java.util.Optional; + +import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo; +import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint; +import org.springframework.ide.vscode.application.properties.metadata.types.Type; +import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil; +import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode; +import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.util.BadLocationException; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.languageserver.util.IDocument; +import org.springframework.ide.vscode.commons.languageserver.util.IRegion; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser; +import org.springframework.ide.vscode.java.properties.parser.ParseResults; +import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key; +import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node; +import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value; + +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +class PropertiesHoverCalculator { + + private FuzzyMap index; + private TypeUtil typeUtil; + private IJavaProject project; + private IDocument doc; + private int offset; + private AntlrParser parser; + + PropertiesHoverCalculator(FuzzyMap index, TypeUtil typeUtil, IJavaProject project, IDocument doc, int offset) { + this.index = index; + this.typeUtil = typeUtil; + this.project = project; + this.doc = doc; + this.offset = offset; + this.parser = new AntlrParser(); + } + + Tuple2 calculate() { + ParseResults parseResults = parser.parse(doc.get()); + Node node = parseResults.ast.findNode(offset); + if (node instanceof Value) { + return getValueHover((Value)node); + } else if (node instanceof Key) { + return getPropertyHover((Key)node); + } + return null; + } + + private DocumentRegion createRegion(IDocument doc, Node value) { + // Trim trailing spaces (there is no leading white space already) + int length = value.getLength(); + try { + length = doc.get(value.getOffset(), value.getLength()).length(); + } catch (BadLocationException e) { + // ignore + } + return new DocumentRegion(doc, value.getOffset(), value.getOffset() + length); + } + + private Tuple2 getPropertyHover(Key property) { + PropertyInfo best = findBestHoverMatch(property.decode()); + if (best == null) { + return null; + } else { + Renderable renderable = new PropertyRenderableProvider(project, best).getRenderable(); + DocumentRegion region = createRegion(doc, property); + return Tuples.of(renderable, region.asRegion()); + } + } + + private Tuple2 getValueHover(Value value) { + DocumentRegion valueRegion = createRegion(doc, value).trimStart(SPACES).trimEnd(SPACES); + if (valueRegion.getStart() <= offset && offset < valueRegion.getEnd()) { + String valueString = valueRegion.toString(); + String propertyName = value.getParent().getKey().decode(); + Type type = getValueType(index, typeUtil, propertyName); + if (TypeUtil.isArray(type) || TypeUtil.isList(type)) { + //It is useful to provide content assist for the values in the list when entering a list + type = TypeUtil.getDomainType(type); + } + if (TypeUtil.isClass(type)) { + //Special case. We want to provide hoverinfos more liberally than what's suggested for completions (i.e. even class names + //that are not suggested by the hints because they do not meet subtyping constraints should be hoverable and linkable! + StsValueHint hint = StsValueHint.className(valueString, typeUtil); + if (hint!=null) { + return Tuples.of(createRenderable(hint), valueRegion.asRegion()); + } + } + //Hack: pretend to invoke content-assist at the end of the value text. This should provide hints applicable to that value + // then show hoverinfo based on that. That way we can avoid duplication a lot of similar logic to compute hoverinfos and hyperlinks. + Collection hints = getValueHints(index, typeUtil, valueString, propertyName, EnumCaseMode.ALIASED); + if (hints!=null) { + Optional hint = hints.stream().filter(h -> valueString.equals(h.getValue())).findFirst(); + if (hint.isPresent()) { + return Tuples.of(createRenderable(hint.get()), valueRegion.asRegion()); + } + } + } + return null; + } + + private Renderable createRenderable(StsValueHint hint) { + return Renderables.htmlBlob((html) -> { + /* + * HACK: javadoc comment from HTML javadoc provider coming from + * generated HTML javadoc is very rich and decorating it further + * with some header like labels just makes it look worse + */ + String descriptionHtml = hint.getDescription().toHtml(); + if (descriptionHtml.indexOf("

      ") == -1) { + // Simple text like description without proper header + html.bold(hint.getValue()); + html.raw("

      "); + html.raw(hint.getDescription().toHtml()); + html.raw("

      "); + } else { + // Description is javadoc-like description from HTML javadoc + html.raw(hint.getDescription().toHtml()); + } + }); + } + + + /** + * Search known properties for the best 'match' to show as hover data. + */ + private PropertyInfo findBestHoverMatch(String propName) { + //TODO: optimize, should be able to use index's treemap to find this without iterating all entries. + PropertyInfo best = null; + int bestCommonPrefixLen = 0; //We try to pick property with longest common prefix + int bestExtraLen = Integer.MAX_VALUE; + for (PropertyInfo candidate : index) { + int commonPrefixLen = StringUtil.commonPrefixLength(propName, candidate.getId()); + int extraLen = candidate.getId().length()-commonPrefixLen; + if (commonPrefixLen==propName.length() && extraLen==0) { + //exact match found, can stop searching for better matches + return candidate; + } + //candidate is better if... + if (commonPrefixLen>bestCommonPrefixLen // it has a longer common prefix + || commonPrefixLen==bestCommonPrefixLen && extraLen getHoverInfo(IDocument document, int offset) throws Exception { + return new PropertiesHoverCalculator(indexProvider.getIndex(document), + typeUtilProvider.getTypeUtil(document), projectFinder.find(document), document, offset).calculate(); + } + + + + +} diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertyRenderableProvider.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertyRenderableProvider.java new file mode 100644 index 000000000..5e7bd2e7d --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/hover/PropertyRenderableProvider.java @@ -0,0 +1,218 @@ +package org.springframework.ide.vscode.application.properties.hover; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo; +import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo.PropertySource; +import org.springframework.ide.vscode.commons.java.IJavaElement; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.IMethod; +import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.util.StringUtil; + + +/** + * Information object that is displayed in SpringPropertiesTextHover's information + * control. + *

      + * Essentially this is a wrapper around {@link ConfigurationMetadataProperty} + * + * @author Kris De Volder + */ +public class PropertyRenderableProvider extends AbstractPropertyRenderableProvider { + + /** + * Java project which is used to find declaration for 'navigate to declaration' action + */ + private IJavaProject javaProject; + + /** + * Data object to display in 'hover text' + */ + private PropertyInfo data; + + public PropertyRenderableProvider(IJavaProject project, PropertyInfo data) { + this.javaProject = project; + this.data = data; + } + + public PropertyInfo getElement() { + return data; + } + + public boolean canOpenDeclaration() { + return getJavaElements()!=null; + } + + /** + * Like 'getSources' but converts raw info into IJavaElements. Raw data which fails to be converted + * is silenetly ignored. + */ + public List getJavaElements() { + try { + if (javaProject!=null) { + List sources = getSources(); + if (!sources.isEmpty()) { + ArrayList elements = new ArrayList(); + for (PropertySource source : sources) { + String typeName = source.getSourceType(); + if (typeName!=null) { + IType type = javaProject.findType(typeName); + IMethod method = null; + if (type!=null) { + String methodSig = source.getSourceMethod(); + if (methodSig!=null) { + method = getMethod(type, methodSig); + } else { + method = getSetter(type, getElement()); + } + } + if (method!=null) { + elements.add(method); + } else if (type!=null) { + elements.add(type); + } + } + } + return elements; + } + } else { + } + } catch (Exception e) { + Log.log(e); + } + return Collections.emptyList(); + } + + /** + * Attempt to find corresponding setter method for a given property. + * @return setter method, or null if not found. + */ + private IMethod getSetter(IType type, PropertyInfo propertyInfo) { + try { + String propName = propertyInfo.getName(); + String setterName = "set" + +Character.toUpperCase(propName.charAt(0)) + +toCamelCase(propName.substring(1)); + String sloppySetterName = setterName.toLowerCase(); + + IMethod sloppyMatch = null; + for (IMethod m : type.getMethods().collect(Collectors.toList())) { + String mname = m.getElementName(); + if (setterName.equals(mname)) { + //found 'exact' name match... done + return m; + } else if (mname.toLowerCase().equals(sloppySetterName)) { + sloppyMatch = m; + } + } + return sloppyMatch; + } catch (Exception e) { + Log.log(e); + return null; + } + } + + /** + * Convert hyphened name to camel case name. It is + * safe to call this on an already camel-cased name. + */ + private String toCamelCase(String name) { + if (name.isEmpty()) { + return name; + } else { + StringBuilder camel = new StringBuilder(); + char[] chars = name.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (c=='-') { + i++; + if (i getSources() { + return data.getSources(); + } + + private IMethod getMethod(IType type, String methodSig) { + int nameEnd = methodSig.indexOf('('); + String name; + if (nameEnd>=0) { + name = methodSig.substring(0, nameEnd); + } else { + name = methodSig; + } + //TODO: This code assumes 0 arguments, which is the case currently for all + // 'real' data in spring jars. + IMethod m = type.getMethod(name, Stream.empty()); + if (m!=null) { + return m; + } + //try find a method with the same name. + return type.getMethods().filter(meth -> name.equals(meth.getElementName())).findFirst().orElse(null); + } + + @Override + protected Object getDefaultValue() { + return data.getDefaultValue(); + } + + @Override + protected IJavaProject getJavaProject() { + return javaProject; + } + + @Override + protected Renderable getDescription() { + String desc = data.getDescription(); + if (StringUtil.hasText(desc)) { + return Renderables.text(desc); + } + return null; + } + + @Override + protected String getType() { + return data.getType(); + } + + @Override + protected String getDeprecationReason() { + return data.getDeprecationReason(); + } + + @Override + protected String getId() { + return data.getId(); + } + + @Override + protected String getDeprecationReplacement() { + return data.getDeprecationReplacement(); + } + + @Override + protected boolean isDeprecated() { + return data.isDeprecated(); + } + +} diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/tools/CommonLanguageTools.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/tools/CommonLanguageTools.java new file mode 100644 index 000000000..f30875eeb --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/tools/CommonLanguageTools.java @@ -0,0 +1,109 @@ +package org.springframework.ide.vscode.application.properties.tools; + +import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo; +import org.springframework.ide.vscode.application.properties.metadata.hints.HintProvider; +import org.springframework.ide.vscode.application.properties.metadata.hints.HintProviders; +import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint; +import org.springframework.ide.vscode.application.properties.metadata.types.Type; +import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser; +import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil; +import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode; +import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.application.properties.reconcile.PropertyNavigator; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.languageserver.util.TextDocument; +import org.springframework.ide.vscode.commons.util.CollectionUtil; +import org.springframework.ide.vscode.commons.util.Log; + +public class CommonLanguageTools { + + public static final Pattern SPACES = Pattern.compile( + "(\\s|\\\\\\s)*" + ); + + public static boolean isValuePrefixChar(char c) { + return !Character.isWhitespace(c) && c!=','; + } + + /** + * Determine the value type for a give propertyName. + */ + public static Type getValueType(FuzzyMap index, TypeUtil typeUtil, String propertyName) { + try { + PropertyInfo prop = index.get(propertyName); + if (prop!=null) { + return TypeParser.parse(prop.getType()); + } else { + prop = CommonLanguageTools.findLongestValidProperty(index, propertyName); + if (prop!=null) { + TextDocument doc = new TextDocument(null); + doc.setText(propertyName); + PropertyNavigator navigator = new PropertyNavigator(doc, null, typeUtil, new DocumentRegion(doc, 0, doc.getLength())); + return navigator.navigate(prop.getId().length(), TypeParser.parse(prop.getType())); + } + } + } catch (Exception e) { + Log.log(e); + } + return null; + } + + public static Collection getValueHints(FuzzyMap index, TypeUtil typeUtil, String query, String propertyName, EnumCaseMode caseMode) { + Type type = getValueType(index, typeUtil, propertyName); + if (TypeUtil.isArray(type) || TypeUtil.isList(type)) { + //It is useful to provide content assist for the values in the list when entering a list + type = TypeUtil.getDomainType(type); + } + List allHints = new ArrayList<>(); + { + Collection hints = typeUtil.getHintValues(type, query, caseMode); + if (CollectionUtil.hasElements(hints)) { + allHints.addAll(hints); + } + } + { + PropertyInfo prop = index.findLongestCommonPrefixEntry(propertyName); + if (prop!=null) { + HintProvider hintProvider = prop.getHints(typeUtil, false); + if (!HintProviders.isNull(hintProvider)) { + allHints.addAll(hintProvider.getValueHints(query)); + } + } + } + return allHints; + } + + /** + * Find the longest known property that is a prefix of the given name. Here prefix does not mean + * 'string prefix' but a prefix in the sense of treating '.' as a kind of separators. So + * 'prefix' is not allowed to end in the middle of a 'segment'. + */ + public static PropertyInfo findLongestValidProperty(FuzzyMap index, String name) { + int bracketPos = name.indexOf('['); + int endPos = bracketPos>=0?bracketPos:name.length(); + PropertyInfo prop = null; + String prefix = null; + while (endPos>0 && prop==null) { + prefix = name.substring(0, endPos); + String canonicalPrefix = camelCaseToHyphens(prefix); + prop = index.get(canonicalPrefix); + if (prop==null) { + endPos = name.lastIndexOf('.', endPos-1); + } + } + if (prop!=null) { + //We should meet caller's expectation that matched properties returned by this method + // match the names exactly even if we found them using relaxed name matching. + return prop.withId(prefix); + } + return null; + } + +} diff --git a/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesEditorTest.java b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesEditorTest.java index 89b5dcc41..bb37712f5 100644 --- a/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesEditorTest.java +++ b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesEditorTest.java @@ -20,6 +20,8 @@ import java.nio.file.Path; import java.time.Duration; import java.util.List; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.Diagnostic; import org.junit.Ignore; import org.junit.Test; import org.springframework.ide.vscode.application.properties.ApplicationPropertiesLanguageServer; @@ -27,7 +29,7 @@ import org.springframework.ide.vscode.application.properties.metadata.CachingVal import org.springframework.ide.vscode.application.properties.metadata.PropertiesLoader; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IType; -import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter.HoverType; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.languageserver.testharness.Editor; @@ -37,9 +39,6 @@ import org.springframework.ide.vscode.properties.editor.test.harness.StyledStrin import com.google.common.collect.ImmutableList; import com.google.common.io.Files; -import org.eclipse.lsp4j.CompletionItem; -import org.eclipse.lsp4j.Diagnostic; - /** * Boot App Properties Editor tests * @@ -48,8 +47,6 @@ import org.eclipse.lsp4j.Diagnostic; */ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { - private JavaProjectFinder javaProjectFinder; - @Test public void testReconcileCatchesParseError() throws Exception { Editor editor = newEditor("key\n"); editor.assertProblems("key|extraneous input"); @@ -171,7 +168,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { } - @Ignore @Test public void testHoverInfos() throws Exception { + @Test public void testHoverInfos() throws Exception { defaultTestData(); Editor editor = newEditor( "#foo\n" + @@ -187,7 +184,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { editor.assertHoverText("logging.", "logging.level"); } - @Ignore @Test public void testHoverInfosWithSpaces() throws Exception { + @Test public void testHoverInfosWithSpaces() throws Exception { defaultTestData(); Editor editor = newEditor( "#foo\n" + @@ -204,7 +201,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { editor.assertHoverText("logging.", "logging.level"); } - @Ignore @Test public void testHoverLongAndShort() throws Exception { + @Test public void testHoverLongAndShort() throws Exception { data("server.port", INTEGER, 8080, "Port where server listens for http."); data("server.port.fancy", BOOLEAN, 8080, "Whether the port is fancy."); Editor editor = newEditor( @@ -917,7 +914,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { ); } - @Ignore @Test public void testDeprecatedPropertyHoverInfo() throws Exception { + @Test public void testDeprecatedPropertyHoverInfo() throws Exception { data("error.path", "java.lang.String", null, "Path of the error controller."); Editor editor = newEditor( "# a comment\n"+ @@ -1334,7 +1331,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { } } - @Ignore @Test public void testClassReferenceCompletion() throws Exception { + @Ignore @Test public void testClassReferenceCompletion() throws Exception { CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); useProject(createPredefinedMavenProject("empty-boot-1.3.0-with-mongo")); @@ -1523,7 +1520,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { ); } - @Ignore @Test public void testEnumJavaDocShownInValueHover() throws Exception { + @Test public void testEnumJavaDocShownInValueHover() throws Exception { useProject(createPredefinedMavenProject("enums-boot-1.3.2-app")); data("my.background", "demo.Color", null, "Color to use as default background."); @@ -1563,6 +1560,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { protected SimpleLanguageServer newLanguageServer() { ApplicationPropertiesLanguageServer server = new ApplicationPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder); server.setMaxCompletionsNumber(-1); + server.setHoverType(HoverType.HTML); return server; }