Hovers for application properties
This commit is contained in:
@@ -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<IMemberValuePair> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<IAnnotation> 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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 = "<init>";
|
||||
|
||||
|
||||
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<IAnnotation> 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<IJavaType> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<IAnnotation> 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<IField> getFields() {
|
||||
return info.fields().stream().map(f -> {
|
||||
return Wrappers.wrap(index, f, javadocProvider);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMethod getMethod(String name, Stream<IJavaType> parameters) {
|
||||
List<Type> 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<IMethod> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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 = "<init>";
|
||||
|
||||
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<IAnnotation> 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<IField> getFields() {
|
||||
return info.fields().stream().map(f -> {
|
||||
return wrap(index, f, javadocProvider);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMethod getMethod(String name, Stream<IJavaType> parameters) {
|
||||
List<Type> typeParameters = parameters.map(Wrappers::from).collect(Collectors.toList());
|
||||
return wrap(index, info.method(name, typeParameters.toArray(new Type[typeParameters.size()])), javadocProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<IMethod> 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<IAnnotation> 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<IAnnotation> 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<IJavaType> 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<IMemberValuePair> 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) {
|
||||
|
||||
@@ -12,6 +12,7 @@ public interface JavadocConstants {
|
||||
char[] FIELD_DETAIL= "<!-- ============ FIELD DETAIL =========== -->".toCharArray(); //$NON-NLS-1$
|
||||
char[] FIELD_SUMMARY = "<!-- =========== FIELD SUMMARY =========== -->".toCharArray(); //$NON-NLS-1$
|
||||
char[] ENUM_CONSTANT_SUMMARY = "<!-- =========== ENUM CONSTANT SUMMARY =========== -->".toCharArray(); //$NON-NLS-1$
|
||||
char[] ENUM_CONSTANT_DETAIL = "<!-- ============ ENUM CONSTANT DETAIL =========== -->".toCharArray();
|
||||
char[] ANNOTATION_TYPE_REQUIRED_MEMBER_SUMMARY = "<!-- =========== ANNOTATION TYPE REQUIRED MEMBER SUMMARY =========== -->".toCharArray(); //$NON-NLS-1$
|
||||
char[] ANNOTATION_TYPE_OPTIONAL_MEMBER_SUMMARY = "<!-- =========== ANNOTATION TYPE OPTIONAL MEMBER SUMMARY =========== -->".toCharArray(); //$NON-NLS-1$
|
||||
char[] END_OF_CLASS_DATA = "<!-- ========= END OF CLASS DATA ========= -->".toCharArray(); //$NON-NLS-1$
|
||||
|
||||
@@ -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 <ul><li> 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 <ul><li> 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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
**/classpath.txt
|
||||
**/bin/**
|
||||
**/*.log.*
|
||||
test-projects/**/.classpath
|
||||
test-projects/**/.project
|
||||
test-projects/**/.factorypath
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PropertyInfo> index, TypeUtil typeUtil, PropertyCompletionFactory completionFactory, IDocument doc, int offset, boolean preferLowerCaseEnums) {
|
||||
public PropertiesCompletionProposalsCalculator(FuzzyMap<PropertyInfo> 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<KeyValuePair> pairs = parser.parse(doc.get()).ast.getNodes(KeyValuePair.class);
|
||||
// return findPair(pairs, offset, 0, pairs.size() - 1);
|
||||
// }
|
||||
//
|
||||
// private KeyValuePair findPair(List<KeyValuePair> 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<StsValueHint> 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<ICompletionProposal> 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<StsValueHint> valueCompletions = getValueHints(query, propertyName, caseMode);
|
||||
Collection<StsValueHint> valueCompletions = getValueHints(index, typeUtil, query, propertyName, caseMode);
|
||||
if (valueCompletions != null && !valueCompletions.isEmpty()) {
|
||||
ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
|
||||
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<StsValueHint> 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<StsValueHint> allHints = new ArrayList<>();
|
||||
{
|
||||
Collection<StsValueHint> 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<Match<PropertyInfo>> findMatches(String prefix) {
|
||||
List<Match<PropertyInfo>> 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 (end<len && isValuePrefixChar(r.charAt(end))) {
|
||||
// end++;
|
||||
// }
|
||||
// r = r.subSequence(start, end);
|
||||
// if (!r.isEmpty()) {
|
||||
// return r.asTypedRegion(IPropertiesFilePartitions.PROPERTY_VALUE);
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 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<bestExtraLen //or same common prefix but fewer extra chars
|
||||
// ) {
|
||||
// bestCommonPrefixLen = commonPrefixLen;
|
||||
// bestExtraLen = extraLen;
|
||||
// best = candidate;
|
||||
// }
|
||||
// }
|
||||
// return best;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* 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<PropertyInfo> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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("<br><br>");
|
||||
buffer.text("Default: ");
|
||||
buffer.raw("<i>");
|
||||
buffer.text(deflt);
|
||||
buffer.raw("</i>");
|
||||
}
|
||||
|
||||
if (isDeprecated()) {
|
||||
buffer.raw("<br><br>");
|
||||
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("<br><br>");
|
||||
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("</"+tag+">");
|
||||
if (StringUtil.hasText(replacement)) {
|
||||
html.text(" -> "+ replacement);
|
||||
}
|
||||
html.raw("<br>");
|
||||
}
|
||||
|
||||
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("<a href=\"http://"+ACTION_HOST+"/");
|
||||
html.url("action-id");
|
||||
html.raw("\">");
|
||||
html.text(displayString);
|
||||
html.raw("</a>");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<PropertyInfo> index;
|
||||
private TypeUtil typeUtil;
|
||||
private IJavaProject project;
|
||||
private IDocument doc;
|
||||
private int offset;
|
||||
private AntlrParser parser;
|
||||
|
||||
PropertiesHoverCalculator(FuzzyMap<PropertyInfo> 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<Renderable, IRegion> 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<Renderable, IRegion> 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<Renderable, IRegion> 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<StsValueHint> hints = getValueHints(index, typeUtil, valueString, propertyName, EnumCaseMode.ALIASED);
|
||||
if (hints!=null) {
|
||||
Optional<StsValueHint> 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("<h4>") == -1) {
|
||||
// Simple text like description without proper header
|
||||
html.bold(hint.getValue());
|
||||
html.raw("<p>");
|
||||
html.raw(hint.getDescription().toHtml());
|
||||
html.raw("</p>");
|
||||
} 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<bestExtraLen //or same common prefix but fewer extra chars
|
||||
) {
|
||||
bestCommonPrefixLen = commonPrefixLen;
|
||||
bestExtraLen = extraLen;
|
||||
best = candidate;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.springframework.ide.vscode.application.properties.hover;
|
||||
|
||||
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
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 reactor.util.function.Tuple2;
|
||||
|
||||
public class PropertiesHoverInfoProvider implements HoverInfoProvider {
|
||||
|
||||
private SpringPropertyIndexProvider indexProvider;
|
||||
private TypeUtilProvider typeUtilProvider;
|
||||
private JavaProjectFinder projectFinder;
|
||||
|
||||
public PropertiesHoverInfoProvider(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder projectFinder) {
|
||||
this.indexProvider = indexProvider;
|
||||
this.typeUtilProvider = typeUtilProvider;
|
||||
this.projectFinder = projectFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tuple2<Renderable, IRegion> getHoverInfo(IDocument document, int offset) throws Exception {
|
||||
return new PropertiesHoverCalculator(indexProvider.getIndex(document),
|
||||
typeUtilProvider.getTypeUtil(document), projectFinder.find(document), document, offset).calculate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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<IJavaElement> getJavaElements() {
|
||||
try {
|
||||
if (javaProject!=null) {
|
||||
List<PropertySource> sources = getSources();
|
||||
if (!sources.isEmpty()) {
|
||||
ArrayList<IJavaElement> elements = new ArrayList<IJavaElement>();
|
||||
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<chars.length) {
|
||||
camel.append(Character.toUpperCase(chars[i]));
|
||||
}
|
||||
} else {
|
||||
camel.append(chars[i]);
|
||||
}
|
||||
}
|
||||
return camel.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 'raw' info about sources that define this property.
|
||||
*/
|
||||
public List<PropertySource> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<PropertyInfo> 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<StsValueHint> getValueHints(FuzzyMap<PropertyInfo> 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<StsValueHint> allHints = new ArrayList<>();
|
||||
{
|
||||
Collection<StsValueHint> 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<PropertyInfo> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.", "<b>logging.level</b>");
|
||||
}
|
||||
|
||||
@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.", "<b>logging.level</b>");
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user