Content assists for application properties

This commit is contained in:
BoykoAlex
2016-11-11 09:20:13 -05:00
parent 01f39c7722
commit 61f7252f15
27 changed files with 1271 additions and 218 deletions

View File

@@ -11,8 +11,22 @@
package org.springframework.ide.vscode.application.properties.metadata; package org.springframework.ide.vscode.application.properties.metadata;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy; import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
/** /**
* A abstract {@link ValueProviderStrategy} that is mean to help speedup successive invocations of * A abstract {@link ValueProviderStrategy} that is mean to help speedup successive invocations of
@@ -53,74 +67,79 @@ public abstract class CachingValueProvider implements ValueProviderStrategy {
*/ */
private int MAX_RESULTS = 500; private int MAX_RESULTS = 500;
// private Cache<Tuple2<String,String>, CacheEntry> cache = createCache(); private Cache<Tuple2<String,String>, CacheEntry> cache = createCache();
//
// private class CacheEntry { private class CacheEntry {
// boolean isComplete = false; boolean isComplete = false;
// int count = 0; int count = 0;
// Flux<StsValueHint> values; Flux<StsValueHint> values;
//
// public CacheEntry(String query, Flux<StsValueHint> producer) { public CacheEntry(String query, Flux<StsValueHint> producer) {
// values = producer values = producer
// .take(MAX_RESULTS) .take(MAX_RESULTS)
// .cache(MAX_RESULTS); .cache(MAX_RESULTS);
// values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max. values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max.
// } }
//
// @Override @Override
// public String toString() { public String toString() {
// return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]"; return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]";
// } }
//
// } }
//
// @Override @Override
// public final Flux<StsValueHint> getValues(IJavaProject javaProject, String query) { public final Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
//// debug("CA query: "+query); Tuple2<String, String> key = key(javaProject, query);
// Tuple2<String, String> key = key(javaProject, query); CacheEntry cached = null;
// CacheEntry cached = cache.get(key); try {
// if (cached==null) { cached = cache.get(key, () -> new CacheEntry(query, getValuesIncremental(javaProject, query)));
// cache.put(key, cached = new CacheEntry(query, getValuesIncremental(javaProject, query))); } catch (ExecutionException e) {
// } Log.log(e);
// return cached.values; }
// } return cached.values;
// }
// /**
// * Tries to use an already cached, complete result for a query that is a prefix of the current query to speed things up. /**
// * <p> * Tries to use an already cached, complete result for a query that is a prefix of the current query to speed things up.
// * Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache. * <p>
// */ * Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache.
// private Flux<StsValueHint> getValuesIncremental(IJavaProject javaProject, String query) { */
//// debug("trying to solve "+query+" incrementally"); private Flux<StsValueHint> getValuesIncremental(IJavaProject javaProject, String query) {
// String subquery = query; // debug("trying to solve "+query+" incrementally");
// while (subquery.length()>=1) { String subquery = query;
// subquery = subquery.substring(0, subquery.length()-1); while (subquery.length()>=1) {
// CacheEntry cached = cache.get(key(javaProject, subquery)); subquery = subquery.substring(0, subquery.length()-1);
// if (cached!=null) { CacheEntry cached = null;
// System.out.println("cached "+subquery+": "+cached); try {
// if (cached.isComplete) { cached = cache.get(key(javaProject, subquery), () -> null);
//// debug("filtering "+subquery+" -> "+query); } catch (ExecutionException | InvalidCacheLoadException e) {
// return cached.values // Log.log(e);
//// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue())) }
// .filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString())); if (cached!=null) {
// } else { System.out.println("cached "+subquery+": "+cached);
//// debug("subquery "+subquery+" cached but is incomplete"); if (cached.isComplete) {
// } return cached.values
// } // .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue()))
// } .filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString()));
//// debug("full search for: "+query); } else {
// return getValuesAsycn(javaProject, query); // debug("subquery "+subquery+" cached but is incomplete");
// } }
// }
// protected abstract Flux<StsValueHint> getValuesAsycn(IJavaProject javaProject, String query); }
// // debug("full search for: "+query);
// private Tuple2<String,String> key(IJavaProject javaProject, String query) { return getValuesAsycn(javaProject, query);
// return Tuples.of(javaProject==null?null:javaProject.getElementName(), query); }
// }
// protected abstract Flux<StsValueHint> getValuesAsycn(IJavaProject javaProject, String query);
// protected <K,V> Cache<K,V> createCache() {
// return new LimitedTimeCache<>(Duration.ofMinutes(1)); private Tuple2<String,String> key(IJavaProject javaProject, String query) {
// } return Tuples.of(javaProject==null?null:javaProject.getElementName(), query);
}
protected <K,V> Cache<K,V> createCache() {
return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
}
public static void restoreDefaults() { public static void restoreDefaults() {
TIMEOUT = DEFAULT_TIMEOUT; TIMEOUT = DEFAULT_TIMEOUT;

View File

@@ -0,0 +1,60 @@
package org.springframework.ide.vscode.application.properties.metadata;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
/**
* @author Kris De Volder
*/
public class ResourceHintProvider implements ValueProviderStrategy {
private static String[] CLASSPATH_PREFIXES = {
"classpath:",
"classpath*:"
};
private static final String[] URL_PREFIXES = new String[] {
"classpath:",
"classpath*:",
"file:",
"http://",
"https://"
};
@Override
public Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
for (String prefix : CLASSPATH_PREFIXES) {
if (query.startsWith(prefix)) {
return classpathHints
.getValues(javaProject, query.substring(prefix.length()))
.map((hint) -> hint.prefixWith(prefix));
}
}
return Flux.fromIterable(urlPrefixHints);
}
final private ImmutableList<StsValueHint> urlPrefixHints = ImmutableList.copyOf(
Arrays.stream(URL_PREFIXES)
.map(StsValueHint::create)
.collect(Collectors.toList())
);
private ClasspathHints classpathHints = new ClasspathHints();
private static class ClasspathHints extends CachingValueProvider {
@Override
protected Flux<StsValueHint> getValuesAsycn(IJavaProject javaProject, String query) {
return Flux.fromStream(javaProject.getClasspath().getClasspathResources().distinct().map(StsValueHint::create));
}
}
}

View File

@@ -97,6 +97,11 @@ public class PropertyCompletionFactory {
return typeUtil.niceTypeName((Type) type); return typeUtil.niceTypeName((Type) type);
} }
@Override
public String getLabel() {
return getBaseDisplayString() + " : " + typeUtil.niceTypeName(getType());
}
}; };
if (property.isDeprecated()) { if (property.isDeprecated()) {
proposal.deprecate(); proposal.deprecate();
@@ -157,6 +162,12 @@ public class PropertyCompletionFactory {
protected String niceTypeName(YType type) { protected String niceTypeName(YType type) {
return typeUtil.niceTypeName(((Type)type)); return typeUtil.niceTypeName(((Type)type));
} }
@Override
public String getLabel() {
return getBaseDisplayString() + " : " + typeUtil.niceTypeName(getType());
}
} }
} }

View File

@@ -26,6 +26,7 @@ import java.util.stream.Stream;
import javax.inject.Provider; import javax.inject.Provider;
import org.springframework.boot.configurationmetadata.Deprecation; import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.application.properties.metadata.ResourceHintProvider;
import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy; import org.springframework.ide.vscode.application.properties.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint; import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.application.properties.metadata.util.DeprecationUtil; import org.springframework.ide.vscode.application.properties.metadata.util.DeprecationUtil;
@@ -577,7 +578,7 @@ public class TypeUtil {
return null; return null;
} }
}); });
// valueHints("org.springframework.core.io.Resource", new ResourceHintProvider()); valueHints("org.springframework.core.io.Resource", new ResourceHintProvider());
} }
/** /**

View File

@@ -90,6 +90,12 @@ public class PropertiesMetadataTestData {
public Stream<Path> getClasspathEntries() throws Exception { public Stream<Path> getClasspathEntries() throws Exception {
return Stream.empty(); return Stream.empty();
} }
@Override
public Stream<String> getClasspathResources() {
return Stream.empty();
}
}); });
for (ConfigurationMetadataProperty propertyInfo : datas.values()) { for (ConfigurationMetadataProperty propertyInfo : datas.values()) {
index.add(propertyInfo); index.add(propertyInfo);

View File

@@ -16,6 +16,7 @@ import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.PrimitiveType; import org.jboss.jandex.PrimitiveType;
import org.jboss.jandex.Type; import org.jboss.jandex.Type;
import org.jboss.jandex.Type.Kind; 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.IAnnotation;
import org.springframework.ide.vscode.commons.java.IField; import org.springframework.ide.vscode.commons.java.IField;
import org.springframework.ide.vscode.commons.java.IJavaType; import org.springframework.ide.vscode.commons.java.IJavaType;
@@ -28,8 +29,6 @@ import org.springframework.ide.vscode.commons.util.HtmlSnippet;
public class Wrappers { public class Wrappers {
private static final int AccEnum = 0x4000;
public static IType wrap(IndexView index, ClassInfo info) { public static IType wrap(IndexView index, ClassInfo info) {
if (info == null) { if (info == null) {
return null; return null;
@@ -75,7 +74,7 @@ public class Wrappers {
@Override @Override
public boolean isEnum() { public boolean isEnum() {
return (info.flags() & AccEnum) != 0; return Flags.isEnum(info.flags());
} }
@Override @Override
@@ -161,7 +160,7 @@ public class Wrappers {
@Override @Override
public boolean isEnumConstant() { public boolean isEnumConstant() {
return (field.flags() & AccEnum) != 0; return Flags.isEnum(field.flags());
} }
@Override @Override

View File

@@ -30,4 +30,10 @@ public interface IClasspath {
*/ */
Stream<Path> getClasspathEntries() throws Exception; Stream<Path> getClasspathEntries() throws Exception;
/**
* Classpath resources paths relative to the source folder path
* @return classpath resource relative paths
*/
Stream<String> getClasspathResources();
} }

View File

@@ -1,10 +1,7 @@
package org.springframework.ide.vscode.application.yaml.completions; package org.springframework.ide.vscode.commons.languageserver.completion;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.util.Log;
/** /**
* Temprary placeholder which sort of replaces the LazyProposalApplier from old STS. * Temprary placeholder which sort of replaces the LazyProposalApplier from old STS.
* It really does nothing right now. Somehow this should be tied into LS protocol so * It really does nothing right now. Somehow this should be tied into LS protocol so
@@ -13,7 +10,7 @@ import org.springframework.ide.vscode.commons.util.Log;
* Right now this is not lazy at all and the completion edits are just computed immediatly. * Right now this is not lazy at all and the completion edits are just computed immediatly.
*/ */
public class LazyProposalApplier { public class LazyProposalApplier {
public static DocumentEdits from(Callable<DocumentEdits> createEdits) throws Exception { public static DocumentEdits from(Callable<DocumentEdits> createEdits) throws Exception {
return createEdits.call(); return createEdits.call();
} }

View File

@@ -27,6 +27,8 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
private final static int MAX_COMPLETIONS = 20; private final static int MAX_COMPLETIONS = 20;
private int maxCompletions = MAX_COMPLETIONS;
final static Logger logger = LoggerFactory.getLogger(VscodeCompletionEngineAdapter.class); final static Logger logger = LoggerFactory.getLogger(VscodeCompletionEngineAdapter.class);
public static final String VS_CODE_CURSOR_MARKER = "{{}}"; public static final String VS_CODE_CURSOR_MARKER = "{{}}";
@@ -39,6 +41,10 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
this.engine = engine; this.engine = engine;
} }
public void setMaxCompletionsNumber(int maxCompletions) {
this.maxCompletions = maxCompletions;
}
@Override @Override
public CompletableFuture<CompletionList> getCompletions(TextDocumentPositionParams params) { public CompletableFuture<CompletionList> getCompletions(TextDocumentPositionParams params) {
//TODO: This returns a CompletableFuture which suggests we should try to do expensive work asyncly. //TODO: This returns a CompletableFuture which suggests we should try to do expensive work asyncly.
@@ -58,7 +64,7 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
int count = 0; int count = 0;
for (ICompletionProposal c : completions) { for (ICompletionProposal c : completions) {
count++; count++;
if (count>MAX_COMPLETIONS) { if (maxCompletions > 0 && count>maxCompletions) {
list.setIsIncomplete(true); list.setIsIncomplete(true);
break; break;
} }

View File

@@ -13,9 +13,11 @@ package org.springframework.ide.vscode.commons.maven.java;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.DirectoryScanner;
import org.springframework.ide.vscode.commons.jandex.JandexIndex; import org.springframework.ide.vscode.commons.jandex.JandexIndex;
import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.java.IType;
@@ -70,5 +72,22 @@ public class MavenProjectClasspath implements IClasspath {
private File findIndexFile(File jarFile) { private File findIndexFile(File jarFile) {
return new File(maven.getIndexFolder().toString(), jarFile.getName() + "-" + jarFile.lastModified() + ".jdx"); return new File(maven.getIndexFolder().toString(), jarFile.getName() + "-" + jarFile.lastModified() + ".jdx");
} }
@Override
public Stream<String> getClasspathResources() {
return project.getBuild().getResources().stream().flatMap(resource -> {
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir(resource.getDirectory());
if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) {
scanner.setIncludes(resource.getIncludes().toArray(new String[resource.getIncludes().size()]));
}
if (resource.getExcludes() != null && !resource.getExcludes().isEmpty()) {
scanner.setExcludes(resource.getExcludes().toArray(new String[resource.getExcludes().size()]));
}
scanner.setCaseSensitive(false);
scanner.scan();
return Arrays.stream(scanner.getIncludedFiles());
});
}
} }

View File

@@ -37,4 +37,9 @@ public class FileClasspath implements IClasspath {
classpathFilePath.getParent().resolve("target/test-classes"))); classpathFilePath.getParent().resolve("target/test-classes")));
} }
@Override
public Stream<String> getClasspathResources() {
return Stream.empty();
}
} }

View File

@@ -52,7 +52,7 @@ keyChar
; ;
separatorAndValue separatorAndValue
: (Space | Colon | Equals) valueChar+ : (Space | Colon | Equals) valueChar*
; ;
valueChar valueChar

View File

@@ -12,10 +12,12 @@ package org.springframework.ide.vscode.java.properties.antlr.parser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.List;
import org.antlr.v4.runtime.ANTLRErrorListener; import org.antlr.v4.runtime.ANTLRErrorListener;
import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ConsoleErrorListener;
import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.Recognizer;
@@ -23,6 +25,7 @@ import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.atn.ATNConfigSet; import org.antlr.v4.runtime.atn.ATNConfigSet;
import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.dfa.DFA;
import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.CommentLineContext; import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.CommentLineContext;
import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.EmptyLineContext;
import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.KeyContext; import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.KeyContext;
import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.PropertyLineContext; import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.PropertyLineContext;
import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.SeparatorAndValueContext; import org.springframework.ide.vscode.java.properties.antlr.parser.JavaPropertiesParser.SeparatorAndValueContext;
@@ -53,6 +56,9 @@ public class AntlrParser implements Parser {
CommonTokenStream tokens = new CommonTokenStream(lexer); CommonTokenStream tokens = new CommonTokenStream(lexer);
JavaPropertiesParser parser = new JavaPropertiesParser(tokens); JavaPropertiesParser parser = new JavaPropertiesParser(tokens);
// To avoid printing parse errors in the console
parser.removeErrorListener(ConsoleErrorListener.INSTANCE);
// Add listener to collect various parser errors // Add listener to collect various parser errors
parser.addErrorListener(new ANTLRErrorListener() { parser.addErrorListener(new ANTLRErrorListener() {
@@ -90,7 +96,9 @@ public class AntlrParser implements Parser {
@Override @Override
public void exitPropertyLine(PropertyLineContext ctx) { public void exitPropertyLine(PropertyLineContext ctx) {
astNodes.add(new KeyValuePair(ctx, key, value)); KeyValuePair pair = new KeyValuePair(ctx, key, value);
key.parent = value.parent = pair;
astNodes.add(pair);
key = null; key = null;
value = null; value = null;
} }
@@ -110,8 +118,13 @@ public class AntlrParser implements Parser {
value = new Value(ctx); value = new Value(ctx);
} }
@Override
public void exitEmptyLine(EmptyLineContext ctx) {
astNodes.add(new EmptyLine(ctx));
}
}); });
parser.parse(); parser.parse();
// Collect and return parse results // Collect and return parse results
@@ -156,6 +169,9 @@ public class AntlrParser implements Parser {
private static abstract class Node implements PropertiesAst.Node { private static abstract class Node implements PropertiesAst.Node {
Node parent;
List<Node> children;
abstract protected ParserRuleContext getContext(); abstract protected ParserRuleContext getContext();
@Override @Override
@@ -167,14 +183,40 @@ public class AntlrParser implements Parser {
public int getLength() { public int getLength() {
return getContext().getStop().getStartIndex() - getOffset() + 1; return getContext().getStop().getStartIndex() - getOffset() + 1;
} }
@Override
public Node getParent() {
return parent;
}
@Override
public List<Node> getChildren() {
return children;
}
} }
private static class Comment implements PropertiesAst.Comment { private static class EmptyLine extends Node implements PropertiesAst.EmptyLine {
private EmptyLineContext context;
public EmptyLine(EmptyLineContext context) {
super();
this.context = context;
}
@Override
protected EmptyLineContext getContext() {
return context;
}
}
private static class Comment extends Node implements PropertiesAst.Comment {
private CommentLineContext context; private CommentLineContext context;
public Comment(CommentLineContext context) { public Comment(CommentLineContext context) {
super();
this.context = context; this.context = context;
} }
@@ -190,6 +232,11 @@ public class AntlrParser implements Parser {
public int getLength() { public int getLength() {
return context.getStop().getStartIndex() - getOffset() + 1; return context.getStop().getStartIndex() - getOffset() + 1;
} }
@Override
protected CommentLineContext getContext() {
return context;
}
} }
@@ -200,9 +247,11 @@ public class AntlrParser implements Parser {
private Value value; private Value value;
public KeyValuePair(PropertyLineContext context, Key key, Value value) { public KeyValuePair(PropertyLineContext context, Key key, Value value) {
super();
this.context = context; this.context = context;
this.key = key; this.key = key;
this.value = value; this.value = value;
this.children = ImmutableList.of(key, value);
} }
protected PropertyLineContext getContext() { protected PropertyLineContext getContext() {
@@ -218,6 +267,18 @@ public class AntlrParser implements Parser {
public Value getValue() { public Value getValue() {
return value; return value;
} }
@Override
public int getLength() {
// Exclude the line break at the end
int length = super.getLength();
String text = getContext().getText();
if (text.charAt(getContext().getStop().getStartIndex() - getOffset()) == '\n') {
length--;
}
return length;
}
} }
private static class Key extends Node implements PropertiesAst.Key { private static class Key extends Node implements PropertiesAst.Key {
@@ -241,6 +302,11 @@ public class AntlrParser implements Parser {
return context.getText().replace("\\:", ":").replace("\\=", "="); return context.getText().replace("\\:", ":").replace("\\=", "=");
} }
} }
@Override
public KeyValuePair getParent() {
return (KeyValuePair) super.getParent();
}
} }
@@ -257,9 +323,9 @@ public class AntlrParser implements Parser {
private void init() { private void init() {
// Remove the separator, if it exists // Remove the separator, if it exists
value = context.getText().replaceAll("^\\s*[:=]?\\s*", ""); value = context.getText().replaceAll("^\\s*[:=]?", "");
// Remove all escaped line breaks with trailing spaces // Remove all escaped line breaks with trailing spaces
decoded = value.replaceAll("\\\\(\r?\n|\r)[ \t\f]*", ""); decoded = value.replaceAll("^\\s*", "").replaceAll("\\\\(\r?\n|\r)[ \t\f]*", "");
try { try {
decoded = PropertiesFileEscapes.unescape(decoded); decoded = PropertiesFileEscapes.unescape(decoded);
} catch (Exception e) { } catch (Exception e) {
@@ -283,6 +349,10 @@ public class AntlrParser implements Parser {
return context.getStart().getStartIndex() + (context.getText().length() - value.length()); return context.getStart().getStartIndex() + (context.getText().length() - value.length());
} }
@Override
public KeyValuePair getParent() {
return (KeyValuePair) super.getParent();
}
} }
} }

View File

@@ -623,20 +623,20 @@ public class JavaPropertiesParser extends Parser {
} else { } else {
consume(); consume();
} }
setState(80); setState(82);
_errHandler.sync(this); _errHandler.sync(this);
_la = _input.LA(1); _la = _input.LA(1);
do { while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << Backslash) | (1L << Colon) | (1L << Equals) | (1L << Exclamation) | (1L << Number) | (1L << Space) | (1L << IdentifierChar))) != 0)) {
{ {
{ {
setState(79); setState(79);
valueChar(); valueChar();
} }
} }
setState(82); setState(84);
_errHandler.sync(this); _errHandler.sync(this);
_la = _input.LA(1); _la = _input.LA(1);
} while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << Backslash) | (1L << Colon) | (1L << Equals) | (1L << Exclamation) | (1L << Number) | (1L << Space) | (1L << IdentifierChar))) != 0) ); }
} }
} }
catch (RecognitionException re) { catch (RecognitionException re) {
@@ -677,56 +677,56 @@ public class JavaPropertiesParser extends Parser {
ValueCharContext _localctx = new ValueCharContext(_ctx, getState()); ValueCharContext _localctx = new ValueCharContext(_ctx, getState());
enterRule(_localctx, 18, RULE_valueChar); enterRule(_localctx, 18, RULE_valueChar);
try { try {
setState(92); setState(93);
switch (_input.LA(1)) { switch (_input.LA(1)) {
case IdentifierChar: case IdentifierChar:
enterOuterAlt(_localctx, 1); enterOuterAlt(_localctx, 1);
{ {
setState(84); setState(85);
match(IdentifierChar); match(IdentifierChar);
} }
break; break;
case Exclamation: case Exclamation:
enterOuterAlt(_localctx, 2); enterOuterAlt(_localctx, 2);
{ {
setState(85); setState(86);
match(Exclamation); match(Exclamation);
} }
break; break;
case Number: case Number:
enterOuterAlt(_localctx, 3); enterOuterAlt(_localctx, 3);
{ {
setState(86); setState(87);
match(Number); match(Number);
} }
break; break;
case Space: case Space:
enterOuterAlt(_localctx, 4); enterOuterAlt(_localctx, 4);
{ {
setState(87); setState(88);
match(Space); match(Space);
} }
break; break;
case Backslash: case Backslash:
enterOuterAlt(_localctx, 5); enterOuterAlt(_localctx, 5);
{ {
setState(88);
match(Backslash);
setState(89); setState(89);
match(Backslash);
setState(90);
match(LineBreak); match(LineBreak);
} }
break; break;
case Equals: case Equals:
enterOuterAlt(_localctx, 6); enterOuterAlt(_localctx, 6);
{ {
setState(90); setState(91);
match(Equals); match(Equals);
} }
break; break;
case Colon: case Colon:
enterOuterAlt(_localctx, 7); enterOuterAlt(_localctx, 7);
{ {
setState(91); setState(92);
match(Colon); match(Colon);
} }
break; break;
@@ -746,31 +746,31 @@ public class JavaPropertiesParser extends Parser {
} }
public static final String _serializedATN = public static final String _serializedATN =
"\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\3\na\4\2\t\2\4\3\t"+ "\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\3\nb\4\2\t\2\4\3\t"+
"\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\3"+ "\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\3"+
"\2\7\2\30\n\2\f\2\16\2\33\13\2\3\2\3\2\3\3\3\3\3\3\5\3\"\n\3\3\4\7\4%"+ "\2\7\2\30\n\2\f\2\16\2\33\13\2\3\2\3\2\3\3\3\3\3\3\5\3\"\n\3\3\4\7\4%"+
"\n\4\f\4\16\4(\13\4\3\4\3\4\3\5\7\5-\n\5\f\5\16\5\60\13\5\3\5\3\5\7\5"+ "\n\4\f\4\16\4(\13\4\3\4\3\4\3\5\7\5-\n\5\f\5\16\5\60\13\5\3\5\3\5\7\5"+
"\64\n\5\f\5\16\5\67\13\5\3\5\3\5\3\6\7\6<\n\6\f\6\16\6?\13\6\3\6\3\6\3"+ "\64\n\5\f\5\16\5\67\13\5\3\5\3\5\3\6\7\6<\n\6\f\6\16\6?\13\6\3\6\3\6\3"+
"\7\3\7\3\7\3\7\3\b\6\bH\n\b\r\b\16\bI\3\t\3\t\3\t\5\tO\n\t\3\n\3\n\6\n"+ "\7\3\7\3\7\3\7\3\b\6\bH\n\b\r\b\16\bI\3\t\3\t\3\t\5\tO\n\t\3\n\3\n\7\n"+
"S\n\n\r\n\16\nT\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\5\13_\n\13\3\13"+ "S\n\n\f\n\16\nV\13\n\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\5\13`\n\13"+
"\2\2\f\2\4\6\b\n\f\16\20\22\24\2\7\3\2\6\7\3\2\b\b\3\3\b\b\3\2\4\5\4\2"+ "\3\13\2\2\f\2\4\6\b\n\f\16\20\22\24\2\7\3\2\6\7\3\2\b\b\3\3\b\b\3\2\4"+
"\4\5\t\tf\2\31\3\2\2\2\4!\3\2\2\2\6&\3\2\2\2\b.\3\2\2\2\n=\3\2\2\2\fB"+ "\5\4\2\4\5\t\tg\2\31\3\2\2\2\4!\3\2\2\2\6&\3\2\2\2\b.\3\2\2\2\n=\3\2\2"+
"\3\2\2\2\16G\3\2\2\2\20N\3\2\2\2\22P\3\2\2\2\24^\3\2\2\2\26\30\5\4\3\2"+ "\2\fB\3\2\2\2\16G\3\2\2\2\20N\3\2\2\2\22P\3\2\2\2\24_\3\2\2\2\26\30\5"+
"\27\26\3\2\2\2\30\33\3\2\2\2\31\27\3\2\2\2\31\32\3\2\2\2\32\34\3\2\2\2"+ "\4\3\2\27\26\3\2\2\2\30\33\3\2\2\2\31\27\3\2\2\2\31\32\3\2\2\2\32\34\3"+
"\33\31\3\2\2\2\34\35\7\2\2\3\35\3\3\2\2\2\36\"\5\6\4\2\37\"\5\b\5\2 \""+ "\2\2\2\33\31\3\2\2\2\34\35\7\2\2\3\35\3\3\2\2\2\36\"\5\6\4\2\37\"\5\b"+
"\5\n\6\2!\36\3\2\2\2!\37\3\2\2\2! \3\2\2\2\"\5\3\2\2\2#%\7\t\2\2$#\3\2"+ "\5\2 \"\5\n\6\2!\36\3\2\2\2!\37\3\2\2\2! \3\2\2\2\"\5\3\2\2\2#%\7\t\2"+
"\2\2%(\3\2\2\2&$\3\2\2\2&\'\3\2\2\2\')\3\2\2\2(&\3\2\2\2)*\5\f\7\2*\7"+ "\2$#\3\2\2\2%(\3\2\2\2&$\3\2\2\2&\'\3\2\2\2\')\3\2\2\2(&\3\2\2\2)*\5\f"+
"\3\2\2\2+-\7\t\2\2,+\3\2\2\2-\60\3\2\2\2.,\3\2\2\2./\3\2\2\2/\61\3\2\2"+ "\7\2*\7\3\2\2\2+-\7\t\2\2,+\3\2\2\2-\60\3\2\2\2.,\3\2\2\2./\3\2\2\2/\61"+
"\2\60.\3\2\2\2\61\65\t\2\2\2\62\64\n\3\2\2\63\62\3\2\2\2\64\67\3\2\2\2"+ "\3\2\2\2\60.\3\2\2\2\61\65\t\2\2\2\62\64\n\3\2\2\63\62\3\2\2\2\64\67\3"+
"\65\63\3\2\2\2\65\66\3\2\2\2\668\3\2\2\2\67\65\3\2\2\289\t\4\2\29\t\3"+ "\2\2\2\65\63\3\2\2\2\65\66\3\2\2\2\668\3\2\2\2\67\65\3\2\2\289\t\4\2\2"+
"\2\2\2:<\7\t\2\2;:\3\2\2\2<?\3\2\2\2=;\3\2\2\2=>\3\2\2\2>@\3\2\2\2?=\3"+ "9\t\3\2\2\2:<\7\t\2\2;:\3\2\2\2<?\3\2\2\2=;\3\2\2\2=>\3\2\2\2>@\3\2\2"+
"\2\2\2@A\7\b\2\2A\13\3\2\2\2BC\5\16\b\2CD\5\22\n\2DE\t\4\2\2E\r\3\2\2"+ "\2?=\3\2\2\2@A\7\b\2\2A\13\3\2\2\2BC\5\16\b\2CD\5\22\n\2DE\t\4\2\2E\r"+
"\2FH\5\20\t\2GF\3\2\2\2HI\3\2\2\2IG\3\2\2\2IJ\3\2\2\2J\17\3\2\2\2KO\7"+ "\3\2\2\2FH\5\20\t\2GF\3\2\2\2HI\3\2\2\2IG\3\2\2\2IJ\3\2\2\2J\17\3\2\2"+
"\n\2\2LM\7\3\2\2MO\t\5\2\2NK\3\2\2\2NL\3\2\2\2O\21\3\2\2\2PR\t\6\2\2Q"+ "\2KO\7\n\2\2LM\7\3\2\2MO\t\5\2\2NK\3\2\2\2NL\3\2\2\2O\21\3\2\2\2PT\t\6"+
"S\5\24\13\2RQ\3\2\2\2ST\3\2\2\2TR\3\2\2\2TU\3\2\2\2U\23\3\2\2\2V_\7\n"+ "\2\2QS\5\24\13\2RQ\3\2\2\2SV\3\2\2\2TR\3\2\2\2TU\3\2\2\2U\23\3\2\2\2V"+
"\2\2W_\7\6\2\2X_\7\7\2\2Y_\7\t\2\2Z[\7\3\2\2[_\7\b\2\2\\_\7\5\2\2]_\7"+ "T\3\2\2\2W`\7\n\2\2X`\7\6\2\2Y`\7\7\2\2Z`\7\t\2\2[\\\7\3\2\2\\`\7\b\2"+
"\4\2\2^V\3\2\2\2^W\3\2\2\2^X\3\2\2\2^Y\3\2\2\2^Z\3\2\2\2^\\\3\2\2\2^]"+ "\2]`\7\5\2\2^`\7\4\2\2_W\3\2\2\2_X\3\2\2\2_Y\3\2\2\2_Z\3\2\2\2_[\3\2\2"+
"\3\2\2\2_\25\3\2\2\2\f\31!&.\65=INT^"; "\2_]\3\2\2\2_^\3\2\2\2`\25\3\2\2\2\f\31!&.\65=INT_";
public static final ATN _ATN = public static final ATN _ATN =
new ATNDeserializer().deserialize(_serializedATN.toCharArray()); new ATNDeserializer().deserialize(_serializedATN.toCharArray());
static { static {

View File

@@ -31,7 +31,7 @@ public final class PropertiesAst {
* Retrieves all AST nodes * Retrieves all AST nodes
* @return List of AST nodes sorted by line number * @return List of AST nodes sorted by line number
*/ */
public List<? extends Node> getAllNodes() { public List<Node> getAllNodes() {
return nodes; return nodes;
} }
@@ -48,6 +48,52 @@ public final class PropertiesAst {
return (List<T>) l; return (List<T>) l;
} }
/**
* Find node in AST corresponding to offset position
* @param offset Position in the text
* @return AST node corresponding to the offset position
*/
public Node findNode(int offset) {
return findNode(nodes, offset, 0, nodes.size() - 1);
}
private Node findNode(List<? extends Node> nodes, int offset, int start, int end) {
if (nodes == null) {
return null;
}
if (start == end) {
Node node = nodes.get(start);
if (node.getOffset() <= offset && offset <= node.getOffset() + node.getLength()) {
Node found = findChildNode(node, offset);
return found == null ? node : found;
} else {
return null;
}
} else if (start < end ) {
int pivotIndex = (start + end) / 2;
Node node = nodes.get(pivotIndex);
if (node.getOffset() > offset) {
return findNode(nodes, offset, start, pivotIndex - 1);
} else if (offset > node.getOffset() + node.getLength()) {
return findNode(nodes, offset, pivotIndex + 1, end);
} else {
Node found = findChildNode(node, offset);
return found == null ? node : found;
}
} else {
return null;
}
}
private Node findChildNode(Node node, int offset) {
if (node.getChildren() == null) {
return null;
} else {
return findNode(node.getChildren(), offset, 0, node.getChildren().size() - 1);
}
}
/** /**
* Java Properties AST node * Java Properties AST node
*/ */
@@ -65,6 +111,18 @@ public final class PropertiesAst {
*/ */
int getLength(); int getLength();
/**
* Node's parent
* @return parent node
*/
Node getParent();
/**
* Node's children
* @return children nodes
*/
List<? extends Node> getChildren();
} }
/** /**
@@ -72,6 +130,13 @@ public final class PropertiesAst {
*/ */
public interface Comment extends Node { public interface Comment extends Node {
}
/**
* AST node for empty line
*/
public interface EmptyLine extends Node {
} }
/** /**
@@ -103,6 +168,8 @@ public final class PropertiesAst {
* @return Decoded property name * @return Decoded property name
*/ */
String decode(); String decode();
KeyValuePair getParent();
} }
@@ -117,6 +184,7 @@ public final class PropertiesAst {
*/ */
String decode(); String decode();
KeyValuePair getParent();
} }
} }

View File

@@ -83,34 +83,6 @@ public class PropertiesAntlrParserTest {
testCommentLine(" # This is comment = ", "# This is comment = "); testCommentLine(" # This is comment = ", "# This is comment = ");
} }
@Test
public void testLines1() throws Exception {
ParseResults results = parser.parse("# Comment\n\n \t \t \n\t\t\n");
assertTrue(results.syntaxErrors.isEmpty());
assertTrue(results.problems.isEmpty());
assertEquals(1, results.ast.getAllNodes().size());
assertEquals(1, results.ast.getNodes(Comment.class).size());
}
@Test
public void testLines2() throws Exception {
ParseResults results = parser.parse("\n\n \t \t \n# Comment\n\t\t\n");
assertTrue(results.syntaxErrors.isEmpty());
assertTrue(results.problems.isEmpty());
assertEquals(1, results.ast.getAllNodes().size());
assertEquals(1, results.ast.getNodes(Comment.class).size());
}
@Test
public void testLines3() throws Exception {
ParseResults results = parser.parse("# Comment\n\nkey = value 1 \n \t \t \n\t\t\n");
assertTrue(results.syntaxErrors.isEmpty());
assertTrue(results.problems.isEmpty());
assertEquals(2, results.ast.getAllNodes().size());
assertEquals(1, results.ast.getNodes(Comment.class).size());
assertEquals(1, results.ast.getNodes(KeyValuePair.class).size());
}
@Test @Test
public void testPropertyWithEqualsSeparator() throws Exception { public void testPropertyWithEqualsSeparator() throws Exception {
testPropertyLine("key=value", "key", "key", "value", "value"); testPropertyLine("key=value", "key", "key", "value", "value");
@@ -118,7 +90,7 @@ public class PropertiesAntlrParserTest {
@Test @Test
public void testPropertyWithEqualsSeparatorAndSpaces() throws Exception { public void testPropertyWithEqualsSeparatorAndSpaces() throws Exception {
testPropertyLine("key \t = \t \tvalue", "key", "key", "value", "value"); testPropertyLine("key \t = \t \tvalue", "key", "key", "value", " \t \tvalue");
} }
@Test @Test
@@ -128,7 +100,7 @@ public class PropertiesAntlrParserTest {
@Test @Test
public void testPropertyWithColonSeparatorAndSpaces() throws Exception { public void testPropertyWithColonSeparatorAndSpaces() throws Exception {
testPropertyLine("key \t : \t \tvalue", "key", "key", "value", "value"); testPropertyLine("key \t : \t \tvalue", "key", "key", "value", " \t \tvalue");
} }
@Test @Test
@@ -168,17 +140,17 @@ public class PropertiesAntlrParserTest {
@Test @Test
public void testEqualsValueSeparatedWithEqualsAndSpace() throws Exception { public void testEqualsValueSeparatedWithEqualsAndSpace() throws Exception {
testPropertyLine("key7 = =", "key7", "key7", "=", "="); testPropertyLine("key7 = =", "key7", "key7", "=", " =");
} }
@Test @Test
public void testValueWithTrailingSpaces() throws Exception { public void testValueWithTrailingSpaces() throws Exception {
testPropertyLine("key = value 1 ", "key", "key", "value 1 ", "value 1 "); testPropertyLine("key = value 1 ", "key", "key", "value 1 ", " value 1 ");
} }
@Test @Test
public void testUnodeCharKeyAndValue() throws Exception { public void testUnodeCharKeyAndValue() throws Exception {
testPropertyLine("k\u2b22ey\u2b28 = val\u2b24ue 1\u2b24 ", "k\u2b22ey\u2b28", "k\u2b22ey\u2b28", "val\u2b24ue 1\u2b24 ", "val\u2b24ue 1\u2b24 "); testPropertyLine("k\u2b22ey\u2b28 = val\u2b24ue 1\u2b24 ", "k\u2b22ey\u2b28", "k\u2b22ey\u2b28", "val\u2b24ue 1\u2b24 ", " val\u2b24ue 1\u2b24 ");
} }
@Test @Test

View File

@@ -0,0 +1,159 @@
package org.springframework.ide.vscode.java.properties.parser.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
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.Parser;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Comment;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.EmptyLine;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.KeyValuePair;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value;
public class PropertiesAstTest {
Parser parser = new AntlrParser();
@Test
public void testLines1() throws Exception {
ParseResults results = parser.parse("# Comment\n\n \t \t \n\t\t\n");
assertTrue(results.syntaxErrors.isEmpty());
assertTrue(results.problems.isEmpty());
assertEquals(4, results.ast.getAllNodes().size());
assertEquals(1, results.ast.getNodes(Comment.class).size());
assertEquals(3, results.ast.getNodes(EmptyLine.class).size());
}
@Test
public void testLines2() throws Exception {
ParseResults results = parser.parse("\n\n \t \t \n# Comment\n\t\t\n");
assertTrue(results.syntaxErrors.isEmpty());
assertTrue(results.problems.isEmpty());
assertEquals(5, results.ast.getAllNodes().size());
assertEquals(1, results.ast.getNodes(Comment.class).size());
assertEquals(4, results.ast.getNodes(EmptyLine.class).size());
}
@Test
public void testLines3() throws Exception {
ParseResults results = parser.parse("# Comment\n\nkey = value 1 \n \t \t \n\t\t\n");
assertTrue(results.syntaxErrors.isEmpty());
assertTrue(results.problems.isEmpty());
assertEquals(5, results.ast.getAllNodes().size());
assertEquals(1, results.ast.getNodes(Comment.class).size());
assertEquals(1, results.ast.getNodes(KeyValuePair.class).size());
assertEquals(3, results.ast.getNodes(EmptyLine.class).size());
}
@Test
public void testLines4() throws Exception {
ParseResults results = parser.parse("# Comment-1\n\nkey = value 1 \n# Comment-2");
assertEquals(4, results.ast.getAllNodes().size());
assertEquals(2, results.ast.getNodes(Comment.class).size());
assertEquals(1, results.ast.getNodes(KeyValuePair.class).size());
assertEquals(1, results.ast.getNodes(EmptyLine.class).size());
}
@Test
public void testLines5() throws Exception {
ParseResults results = parser.parse("#comment\nliquibase.enabled=\n#comment");
assertEquals(3, results.ast.getAllNodes().size());
assertEquals(2, results.ast.getNodes(Comment.class).size());
assertEquals(1, results.ast.getNodes(KeyValuePair.class).size());
}
@Test
public void positionComment() throws Exception {
ParseResults results = parser.parse("# Comment\n" + "key = value\n");
Node node = results.ast.findNode(7);
assertTrue(node instanceof Comment);
assertTrue(node.getOffset() <= 7 && 7 <= node.getOffset() + node.getLength());
node = results.ast.findNode(9);
assertTrue(node instanceof Comment);
assertTrue(node.getOffset() <= 9 && 9 <= node.getOffset() + node.getLength());
node = results.ast.findNode(0);
assertTrue(node instanceof Comment);
assertTrue(node.getOffset() <= 0 && 0 <= node.getOffset() + node.getLength());
}
@Test
public void positionEmptyLine() throws Exception {
ParseResults results = parser.parse("# Comment\n" + "key = value\n" + "\t\n");
Node node = results.ast.findNode(23);
assertTrue(node instanceof EmptyLine);
assertTrue(node.getOffset() <= 23 && 23 <= node.getOffset() + node.getLength());
node = results.ast.findNode(24);
assertTrue(node instanceof EmptyLine);
assertTrue(node.getOffset() <= 24 && 24 <= node.getOffset() + node.getLength());
}
@Test
public void positionKey() throws Exception {
ParseResults results = parser.parse("# Comment\n" + "key = value\n" + "\t\n");
Node node = results.ast.findNode(10);
assertTrue(node instanceof Key);
assertTrue(node.getOffset() <= 10 && 10 <= node.getOffset() + node.getLength());
node = results.ast.findNode(12);
assertTrue(node instanceof Key);
assertTrue(node.getOffset() <= 12 && 12 <= node.getOffset() + node.getLength());
node = results.ast.findNode(13);
assertTrue(node instanceof Key);
assertTrue(node.getOffset() <= 13 && 13 <= node.getOffset() + node.getLength());
}
@Test
public void positionPair() throws Exception {
ParseResults results = parser.parse("# Comment\n" + "key = value\n" + "\t\n");
Node node = results.ast.findNode(15);
assertTrue(node instanceof KeyValuePair);
assertTrue(node.getOffset() <= 15 && 15 <= node.getOffset() + node.getLength());
}
@Test
public void positionValue() throws Exception {
ParseResults results = parser.parse("# Comment\n" + "key = value\n");
Node node = results.ast.findNode(17);
assertTrue(node instanceof Value);
assertTrue(node.getOffset() <= 17 && 17 <= node.getOffset() + node.getLength());
node = results.ast.findNode(22);
assertTrue(node instanceof Value);
assertTrue(node.getOffset() <= 22 && 22 <= node.getOffset() + node.getLength());
node = results.ast.findNode(16);
assertTrue(node instanceof Value);
assertTrue(node.getOffset() <= 16 && 16 <= node.getOffset() + node.getLength());
}
@Test
public void positionValueEofAtEnd() throws Exception {
ParseResults results = parser.parse("# Comment\n" + "key = value");
Node node = results.ast.findNode(22);
assertTrue(node instanceof Value);
assertTrue(node.getOffset() <= 22 && 22 <= node.getOffset() + node.getLength());
node = results.ast.findNode(16);
assertTrue(node instanceof Value);
assertTrue(node.getOffset() <= 16 && 16 <= node.getOffset() + node.getLength());
}
@Test
public void positionEmptyValue() throws Exception {
ParseResults results = parser.parse("# Comment\n" + "key =");
Node node = results.ast.findNode(16);
assertTrue(node instanceof Value);
assertTrue(node.getOffset() <= 16 && 16 <= node.getOffset() + node.getLength());
}
}

View File

@@ -12,10 +12,12 @@ package org.springframework.ide.vscode.application.properties;
import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentSyncKind; import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.springframework.ide.vscode.application.properties.completions.SpringPropertiesCompletionEngine;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider; 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.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.application.properties.reconcile.SpringPropertiesReconcileEngine; import org.springframework.ide.vscode.application.properties.reconcile.SpringPropertiesReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter;
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.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.util.TextDocument; import org.springframework.ide.vscode.commons.languageserver.util.TextDocument;
@@ -30,18 +32,38 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer {
private SpringPropertyIndexProvider indexProvider; private SpringPropertyIndexProvider indexProvider;
private TypeUtilProvider typeUtilProvider; private TypeUtilProvider typeUtilProvider;
private VscodeCompletionEngineAdapter completionEngine;
private SpringPropertiesReconcileEngine reconcileEngine;
public ApplicationPropertiesLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider) { public ApplicationPropertiesLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder javaProjectFinder) {
this.indexProvider = indexProvider; this.indexProvider = indexProvider;
this.typeUtilProvider = typeUtilProvider; this.typeUtilProvider = typeUtilProvider;
SimpleTextDocumentService documents = getTextDocumentService(); SimpleTextDocumentService documents = getTextDocumentService();
IReconcileEngine reconcileEngine = getReconcileEngine(); reconcileEngine = getReconcileEngine();
documents.onDidChangeContent(params -> { documents.onDidChangeContent(params -> {
TextDocument doc = params.getDocument(); TextDocument doc = params.getDocument();
validateWith(doc, reconcileEngine); validateWith(doc, reconcileEngine);
}); });
SpringPropertiesCompletionEngine propertiesCompletionEngine = new SpringPropertiesCompletionEngine(
indexProvider,
typeUtilProvider,
javaProjectFinder
);
completionEngine = new VscodeCompletionEngineAdapter(this, propertiesCompletionEngine);
documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion);
}
public void setMaxCompletionsNumber(int number) {
completionEngine.setMaxCompletionsNumber(number);
}
public void setRecordSyntaxErrors(boolean record) {
reconcileEngine.setRecordSyntaxErrors(record);
} }
@Override @Override
@@ -53,7 +75,7 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer {
return c; return c;
} }
protected IReconcileEngine getReconcileEngine() { protected SpringPropertiesReconcileEngine getReconcileEngine() {
return new SpringPropertiesReconcileEngine(indexProvider, typeUtilProvider); return new SpringPropertiesReconcileEngine(indexProvider, typeUtilProvider);
} }

View File

@@ -11,7 +11,6 @@
package org.springframework.ide.vscode.application.properties; package org.springframework.ide.vscode.application.properties;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Logger;
import org.eclipse.lsp4j.services.LanguageServer; import org.eclipse.lsp4j.services.LanguageServer;
import org.springframework.ide.vscode.application.properties.metadata.DefaultSpringPropertyIndexProvider; import org.springframework.ide.vscode.application.properties.metadata.DefaultSpringPropertyIndexProvider;
@@ -36,7 +35,7 @@ public class Main {
JavaProjectFinder javaProjectFinder = JavaProjectFinder.DEFAULT; JavaProjectFinder javaProjectFinder = JavaProjectFinder.DEFAULT;
SpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder); SpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder);
TypeUtilProvider typeUtilProvider = (IDocument doc) -> new TypeUtil(javaProjectFinder.find(doc)); TypeUtilProvider typeUtilProvider = (IDocument doc) -> new TypeUtil(javaProjectFinder.find(doc));
LanguageServer server = new ApplicationPropertiesLanguageServer(indexProvider, typeUtilProvider); LanguageServer server = new ApplicationPropertiesLanguageServer(indexProvider, typeUtilProvider, javaProjectFinder);
return server; return server;
}); });
} }

View File

@@ -0,0 +1,582 @@
package org.springframework.ide.vscode.application.properties.completions;
import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens;
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;
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.hints.ValueHintHoverInfo;
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.BeanPropertyNameMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.application.properties.reconcile.PropertyNavigator;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.completion.LazyProposalApplier;
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.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;
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.EmptyLine;
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 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!=',';
}
private static final PrefixFinder valuePrefixFinder = new PrefixFinder() {
protected boolean isPrefixChar(char c) {
return isValuePrefixChar(c);
}
};
private static final PrefixFinder fuzzySearchPrefix = new PrefixFinder() {
protected boolean isPrefixChar(char c) {
return !Character.isWhitespace(c);
}
};
private static final PrefixFinder navigationPrefixFinder = new PrefixFinder() {
public String getPrefix(IDocument doc, int offset) {
String prefix = super.getPrefix(doc, offset);
//Check if character before looks like 'navigation'.. otherwise don't
// return a navigationPrefix.
char charBefore = getCharBefore(doc, prefix, offset);
if (charBefore=='.' || charBefore==']') {
return prefix;
}
return null;
}
private char getCharBefore(IDocument doc, String prefix, int offset) {
try {
if (prefix!=null) {
int offsetBefore = offset-prefix.length()-1;
if (offsetBefore>=0) {
return doc.getChar(offsetBefore);
}
}
} catch (BadLocationException e) {
//ignore
}
return 0;
}
protected boolean isPrefixChar(char c) {
return !Character.isWhitespace(c) && c!=']' && c!=']' && c!='.';
}
};
private FuzzyMap<PropertyInfo> index;
private TypeUtil typeUtil;
private PropertyCompletionFactory completionFactory;
private IDocument doc;
private int offset;
private boolean preferLowerCaseEnums;
private AntlrParser parser;
PropertiesCompletionProposalsCalculator(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, PropertyCompletionFactory completionFactory, IDocument doc, int offset, boolean preferLowerCaseEnums) {
this.index = index;
this.typeUtil = typeUtil;
this.completionFactory = completionFactory;
this.doc = doc;
this.offset = offset;
this.preferLowerCaseEnums = preferLowerCaseEnums;
this.parser = new AntlrParser();
}
/**
* Create completions proposals in the context of a properties text editor.
*/
public Collection<ICompletionProposal> calculate() throws BadLocationException {
ParseResults parseResults = parser.parse(doc.get());
Node node = parseResults.ast.findNode(offset);
if (node instanceof Value) {
return getValueCompletions((Value)node);
} else if (node instanceof Key || node instanceof EmptyLine || node == null) {
return getPropertyCompletions();
}
return Collections.emptyList();
}
private Collection<ICompletionProposal> getNavigationProposals() {
String navPrefix = navigationPrefixFinder.getPrefix(doc, offset);
try {
if (navPrefix!=null) {
int navOffset = offset-navPrefix.length()-1; //offset of 'nav' operator char (i.e. '.' or ']').
navPrefix = fuzzySearchPrefix.getPrefix(doc, navOffset);
if (navPrefix!=null && !navPrefix.isEmpty()) {
PropertyInfo prop = findLongestValidProperty(index, navPrefix);
if (prop!=null) {
int regionStart = navOffset-navPrefix.length();
Collection<ICompletionProposal> hintProposals = getKeyHintProposals(prop, navOffset);
if (CollectionUtil.hasElements(hintProposals)) {
return hintProposals;
}
PropertyNavigator navigator = new PropertyNavigator(doc, null, typeUtil, new DocumentRegion(doc, regionStart, navOffset));
Type type = navigator.navigate(regionStart+prop.getId().length(), TypeParser.parse(prop.getType()));
if (type!=null) {
return getNavigationProposals(type, navOffset);
}
}
}
}
} catch (Exception e) {
Log.log(e);
}
return Collections.emptyList();
}
private Collection<ICompletionProposal> getKeyHintProposals(PropertyInfo prop, int navOffset) {
HintProvider hintProvider = prop.getHints(typeUtil, false);
if (!HintProviders.isNull(hintProvider)) {
String query = textBetween(doc, navOffset+1, offset);
List<TypedProperty> hintProperties = hintProvider.getPropertyHints(query);
if (CollectionUtil.hasElements(hintProperties)) {
return createPropertyProposals(TypeParser.parse(prop.getType()), navOffset, query, hintProperties);
}
}
return ImmutableList.of();
}
private String textBetween(IDocument doc, int start, int end) {
if (end > doc.getLength()) {
end = doc.getLength();
}
if (start>doc.getLength()) {
start = doc.getLength();
}
if (start<0) {
start = 0;
}
if (end < 0) {
end = 0;
}
if (start<end) {
try {
return doc.get(start, end-start);
} catch (BadLocationException e) {
}
}
return "";
}
/**
* @param type Type of the expression leading upto the 'nav' operator
* @param navOffset Offset of the nav operator (either ']' or '.'
* @param offset Offset of the cursor where CA was requested.
*/
private Collection<ICompletionProposal> getNavigationProposals(Type type, int navOffset) {
try {
char navOp = doc.getChar(navOffset);
if (navOp=='.') {
String prefix = doc.get(navOffset+1, offset-(navOffset+1));
EnumCaseMode caseMode = caseMode(prefix);
List<TypedProperty> objectProperties = typeUtil.getProperties(type, caseMode, BeanPropertyNameMode.HYPHENATED);
//Note: properties editor itself deals with relaxed names. So it expects the properties here to be returned in hyphenated form only.
if (objectProperties!=null && !objectProperties.isEmpty()) {
return createPropertyProposals(type, navOffset, prefix, objectProperties);
}
} else {
//TODO: other cases ']' or '[' ?
}
} catch (Exception e) {
Log.log(e);
}
return Collections.emptyList();
}
protected Collection<ICompletionProposal> createPropertyProposals(Type type, int navOffset,
String prefix, List<TypedProperty> objectProperties) {
ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
for (TypedProperty prop : objectProperties) {
double score = FuzzyMatcher.matchScore(prefix, prop.getName());
if (score!=0) {
Type valueType = prop.getType();
String postFix = propertyCompletionPostfix(typeUtil, valueType);
DocumentEdits edits = new DocumentEdits(doc);
edits.delete(navOffset+1, offset);
edits.insert(offset, prop.getName()+postFix);
proposals.add(
completionFactory.beanProperty(doc, null, type, prefix, prop, score, edits, typeUtil)
);
}
}
return proposals;
}
/**
* Determines the EnumCaseMode used to generate completion candidates based on prefix.
*/
protected EnumCaseMode caseMode(String prefix) {
EnumCaseMode caseMode;
if ("".equals(prefix)) {
caseMode = preferLowerCaseEnums?EnumCaseMode.LOWER_CASE:EnumCaseMode.ORIGNAL;
} else {
caseMode = Character.isLowerCase(prefix.charAt(0))?EnumCaseMode.LOWER_CASE:EnumCaseMode.ORIGNAL;
}
return caseMode;
}
protected static String propertyCompletionPostfix(TypeUtil typeUtil, Type type) {
String postfix = "";
if (type!=null) {
if (typeUtil.isAssignableType(type)) {
postfix = "=";
} else if (TypeUtil.isBracketable(type)) {
postfix = "[";
} else if (typeUtil.isDotable(type)) {
postfix = ".";
}
}
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());
int startOfValue = offset - query.length();
EnumCaseMode caseMode = caseMode(query);
// note: no need to skip whitespace backwards.
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);
if (valueCompletions != null && !valueCompletions.isEmpty()) {
ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
for (StsValueHint hint : valueCompletions) {
String valueCandidate = hint.getValue();
double score = FuzzyMatcher.matchScore(query, valueCandidate);
if (score != 0) {
DocumentEdits edits = new DocumentEdits(doc);
edits.delete(startOfValue, offset);
edits.insert(offset, valueCandidate);
proposals.add(completionFactory.valueProposal(valueCandidate, query, getValueType(propertyName),
score, edits, new ValueHintHoverInfo(hint))
// new ValueProposal(startOfValue, valuePrefix,
// valueCandidate, i)
);
}
}
return proposals;
}
}
return Collections.emptyList();
}
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()).trim().length();
} catch (BadLocationException e) {
// ignore
}
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;
}
private Collection<ICompletionProposal> getPropertyCompletions() throws BadLocationException {
Collection<ICompletionProposal> navProposals = getNavigationProposals();
if (!navProposals.isEmpty()) {
return navProposals;
}
return getFuzzyCompletions();
}
protected Collection<ICompletionProposal> getFuzzyCompletions() {
final String prefix = fuzzySearchPrefix.getPrefix(doc, offset);
if (prefix != null) {
Collection<Match<PropertyInfo>> matches = findMatches(prefix);
if (matches!=null && !matches.isEmpty()) {
ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(matches.size());
for (final Match<PropertyInfo> match : matches) {
DocumentEdits docEdits;
try {
docEdits = LazyProposalApplier.from(() -> {
Type type = TypeParser.parse(match.data.getType());
DocumentEdits edits = new DocumentEdits(doc);
edits.delete(offset-prefix.length(), offset);
edits.insert(offset, match.data.getId() + propertyCompletionPostfix(typeUtil, type));
return edits;
});
proposals.add(completionFactory.property(doc, docEdits, match, typeUtil));
} catch (Exception e) {
Log.log(e);
}
}
return proposals;
}
}
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;
}
}

View File

@@ -0,0 +1,51 @@
package org.springframework.ide.vscode.application.properties.completions;
import java.util.Collection;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.application.properties.metadata.completions.PropertyCompletionFactory;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.BadLocationException;
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;;
/**
* @author Kris De Volder
*/
public class SpringPropertiesCompletionEngine implements ICompletionEngine {
private boolean preferLowerCaseEnums = true; //might make sense to make this user configurable
private SpringPropertyIndexProvider indexProvider;
private TypeUtilProvider typeUtilProvider;
private PropertyCompletionFactory completionFactory = null;
/**
* Constructor used in 'production'. Wires up stuff properly for running inside a normal
* Eclipse runtime.
*/
public SpringPropertiesCompletionEngine(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder projectFinder) {
this.indexProvider = indexProvider;
this.typeUtilProvider = typeUtilProvider;
this.completionFactory = new PropertyCompletionFactory(projectFinder);
}
/**
* Create completions proposals in the context of a properties text editor.
*/
public Collection<ICompletionProposal> getCompletions(IDocument doc, int offset) throws BadLocationException {
return new PropertiesCompletionProposalsCalculator(indexProvider.getIndex(doc),
typeUtilProvider.getTypeUtil(doc), completionFactory, doc, offset, preferLowerCaseEnums).calculate();
}
public boolean getPreferLowerCaseEnums() {
return preferLowerCaseEnums;
}
public void setPreferLowerCaseEnums(boolean preferLowerCaseEnums) {
this.preferLowerCaseEnums = preferLowerCaseEnums;
}
}

View File

@@ -69,7 +69,7 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine {
private final DelimitedListReconciler commaListReconciler = new DelimitedListReconciler(COMMA, this::reconcileType); private final DelimitedListReconciler commaListReconciler = new DelimitedListReconciler(COMMA, this::reconcileType);
private Parser parser = new AntlrParser(); private Parser parser = new AntlrParser();
private boolean recordSyntaxErrors = false; private boolean recordSyntaxErrors;
public SpringPropertiesReconcileEngine(SpringPropertyIndexProvider provider, TypeUtilProvider typeUtilProvider) { public SpringPropertiesReconcileEngine(SpringPropertyIndexProvider provider, TypeUtilProvider typeUtilProvider) {
this(provider, typeUtilProvider, true); this(provider, typeUtilProvider, true);
@@ -103,8 +103,6 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine {
} }
results.ast.getNodes(KeyValuePair.class).forEach(pair -> { results.ast.getNodes(KeyValuePair.class).forEach(pair -> {
// Key fullName = pair.getKey();
// String keyName = fullName.decode();
try { try {
DocumentRegion propertyNameRegion = createRegion(doc, pair.getKey()); DocumentRegion propertyNameRegion = createRegion(doc, pair.getKey());
String keyName = PropertiesFileEscapes.unescape(propertyNameRegion.toString()); String keyName = PropertiesFileEscapes.unescape(propertyNameRegion.toString());
@@ -184,7 +182,7 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine {
// Trim trailing spaces (there is no leading white space already) // Trim trailing spaces (there is no leading white space already)
int length = value.getLength(); int length = value.getLength();
try { try {
length = doc.get(value.getOffset(), value.getLength()).trim().length(); length = doc.get(value.getOffset(), value.getLength()).length();
} catch (BadLocationException e) { } catch (BadLocationException e) {
// ignore // ignore
} }

View File

@@ -27,6 +27,7 @@ import org.springframework.ide.vscode.application.properties.metadata.CachingVal
import org.springframework.ide.vscode.application.properties.metadata.PropertiesLoader; import org.springframework.ide.vscode.application.properties.metadata.PropertiesLoader;
import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.java.IType;
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.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.languageserver.testharness.Editor;
@@ -47,6 +48,8 @@ import org.eclipse.lsp4j.Diagnostic;
*/ */
public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
private JavaProjectFinder javaProjectFinder;
@Test public void testReconcileCatchesParseError() throws Exception { @Test public void testReconcileCatchesParseError() throws Exception {
Editor editor = newEditor("key\n"); Editor editor = newEditor("key\n");
editor.assertProblems("key|extraneous input"); editor.assertProblems("key|extraneous input");
@@ -66,28 +69,28 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
editor.assertProblems("problem|extraneous input", "another|mismatched input"); editor.assertProblems("problem|extraneous input", "another|mismatched input");
} }
@Ignore @Test public void testServerPortCompletion() throws Exception { @Test public void testServerPortCompletion() throws Exception {
data("server.port", INTEGER, 8080, "Port where server listens for http."); data("server.port", INTEGER, 8080, "Port where server listens for http.");
assertCompletion("ser<*>", "server.port=<*>"); assertCompletion("ser<*>", "server.port=<*>");
assertCompletionDisplayString("ser<*>", "server.port : int"); assertCompletionDisplayString("ser<*>", "server.port : int");
} }
@Ignore @Test public void testLoggingLevelCompletion() throws Exception { @Test public void testLoggingLevelCompletion() throws Exception {
data("logging.level", "java.util.Map<java.lang.String,java.lang.Object>", null, "Logging level per package."); data("logging.level", "java.util.Map<java.lang.String,java.lang.Object>", null, "Logging level per package.");
assertCompletion("lolev<*>","logging.level.<*>"); assertCompletion("lolev<*>","logging.level.<*>");
} }
@Ignore @Test public void testListCompletion() throws Exception { @Test public void testListCompletion() throws Exception {
data("foo.bars", "java.util.List<java.lang.String>", null, "List of bars in foo."); data("foo.bars", "java.util.List<java.lang.String>", null, "List of bars in foo.");
assertCompletion("foba<*>","foo.bars=<*>"); assertCompletion("foba<*>","foo.bars=<*>");
} }
@Ignore @Test public void testInetAddresCompletion() throws Exception { @Test public void testInetAddresCompletion() throws Exception {
defaultTestData(); defaultTestData();
assertCompletion("server.add<*>", "server.address=<*>"); assertCompletion("server.add<*>", "server.address=<*>");
} }
@Ignore @Test public void testStringArrayCompletion() throws Exception { @Test public void testStringArrayCompletion() throws Exception {
data("spring.freemarker.view-names", "java.lang.String[]", null, "White list of view names that can be resolved."); data("spring.freemarker.view-names", "java.lang.String[]", null, "White list of view names that can be resolved.");
data("some.defaulted.array", "java.lang.String[]", new String[] {"a", "b", "c"} , "Stuff."); data("some.defaulted.array", "java.lang.String[]", new String[] {"a", "b", "c"} , "Stuff.");
@@ -95,7 +98,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
assertCompletion("some.d.a<*>", "some.defaulted.array=<*>"); assertCompletion("some.d.a<*>", "some.defaulted.array=<*>");
} }
@Ignore @Test public void testEmptyPrefixProposalsSortedAlpabetically() throws Exception { @Test public void testEmptyPrefixProposalsSortedAlpabetically() throws Exception {
defaultTestData(); defaultTestData();
Editor editor = newEditor(""); Editor editor = newEditor("");
List<CompletionItem> completions = editor.getCompletions(); List<CompletionItem> completions = editor.getCompletions();
@@ -110,7 +113,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
} }
} }
@Ignore @Test public void testValueCompletion() throws Exception { @Test public void testValueCompletion() throws Exception {
defaultTestData(); defaultTestData();
assertCompletionsVariations("liquibase.enabled=<*>", assertCompletionsVariations("liquibase.enabled=<*>",
"liquibase.enabled=false<*>", "liquibase.enabled=false<*>",
@@ -310,7 +313,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testPojoArrayCompletions() throws Exception { @Test public void testPojoArrayCompletions() throws Exception {
IJavaProject p = createPredefinedMavenProject("boot-1.2.1-app-properties-list-of-pojo"); IJavaProject p = createPredefinedMavenProject("boot-1.2.1-app-properties-list-of-pojo");
useProject(p); useProject(p);
@@ -395,7 +398,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testRelaxedNameContentAssist() throws Exception { @Test public void testRelaxedNameContentAssist() throws Exception {
data("foo-bar-zor.enabled", "java.lang.Boolean", null, null); data("foo-bar-zor.enabled", "java.lang.Boolean", null, null);
assertCompletion("fooBar<*>", "foo-bar-zor.enabled=<*>"); assertCompletion("fooBar<*>", "foo-bar-zor.enabled=<*>");
} }
@@ -458,7 +461,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testEnumPropertyCompletionInsideCommaSeparateList() throws Exception { @Test public void testEnumPropertyCompletionInsideCommaSeparateList() throws Exception {
IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app"); IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(p); useProject(p);
@@ -481,7 +484,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
assertCompletion("foo.colors=RED,B<*>", "foo.colors=RED,BLUE<*>"); assertCompletion("foo.colors=RED,B<*>", "foo.colors=RED,BLUE<*>");
} }
@Ignore @Test public void testEnumPropertyCompletion() throws Exception { @Test public void testEnumPropertyCompletion() throws Exception {
IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app"); IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(p); useProject(p);
@@ -523,7 +526,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testEnumMapValueCompletion() throws Exception { @Test public void testEnumMapValueCompletion() throws Exception {
IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app"); IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(p); useProject(p);
@@ -559,7 +562,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testEnumMapKeyCompletion() throws Exception { @Test public void testEnumMapKeyCompletion() throws Exception {
IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app"); IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(p); useProject(p);
@@ -617,7 +620,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testPojoCompletions() throws Exception { @Test public void testPojoCompletions() throws Exception {
IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app"); IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(p); useProject(p);
@@ -629,11 +632,11 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
assertCompletionsDisplayString("foo.data.", assertCompletionsDisplayString("foo.data.",
"wavelen : double", "wavelen : double",
"name : String", "name : String",
"next : demo.Color[RED, GREEN, BLUE]", "next : demo.Color[BLUE, GREEN, RED]",
"nested : demo.ColorData", "nested : demo.ColorData",
"children : List<demo.ColorData>", "children : List<demo.ColorData>",
"mapped-children : Map<String, demo.ColorData>", "mapped-children : Map<String, demo.ColorData>",
"color-children : Map<demo.Color[RED, GREEN, BLUE], demo.ColorData>", "color-children : Map<demo.Color[BLUE, GREEN, RED], demo.ColorData>",
"tags : List<String>", "tags : List<String>",
"funky : boolean" "funky : boolean"
); );
@@ -675,7 +678,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testListOfAtomicCompletions() throws Exception { @Test public void testListOfAtomicCompletions() throws Exception {
data("foo.slist", "java.util.List<java.lang.String>", null, "list of strings"); data("foo.slist", "java.util.List<java.lang.String>", null, "list of strings");
data("foo.ulist", "java.util.List<Unknown>", null, "list of strings"); data("foo.ulist", "java.util.List<Unknown>", null, "list of strings");
data("foo.dlist", "java.util.List<java.lang.Double>", null, "list of doubles"); data("foo.dlist", "java.util.List<java.lang.Double>", null, "list of doubles");
@@ -684,7 +687,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
assertCompletionsVariations("foo.sl<*>", "foo.slist=<*>"); assertCompletionsVariations("foo.sl<*>", "foo.slist=<*>");
} }
@Ignore @Test public void testMapKeyDotInterpretation() throws Exception { @Test public void testMapKeyDotInterpretation() throws Exception {
//Interpretation of '.' changes depending on the domain type (i.e. when domain type is //Interpretation of '.' changes depending on the domain type (i.e. when domain type is
//is a simple type got which '.' navigation is invalid then the '.' is 'eaten' by the key. //is a simple type got which '.' navigation is invalid then the '.' is 'eaten' by the key.
@@ -724,7 +727,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testMapKeyDotInterpretationInPojo() throws Exception { @Test public void testMapKeyDotInterpretationInPojo() throws Exception {
//Similar to testMapKeyDotInterpretation but this time maps are not attached to property //Similar to testMapKeyDotInterpretation but this time maps are not attached to property
// directly but via a pojo property // directly but via a pojo property
@@ -797,7 +800,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testEnumsInLowerCaseContentAssist() throws Exception { @Test public void testEnumsInLowerCaseContentAssist() throws Exception {
IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app"); IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(p); useProject(p);
@@ -840,7 +843,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
assertCompletionsVariations("foo.color-data.red.na<*>", "foo.color-data.red.name=<*>"); assertCompletionsVariations("foo.color-data.red.na<*>", "foo.color-data.red.name=<*>");
} }
@Ignore @Test public void testNavigationProposalAfterRelaxedPropertyName() throws Exception { @Test public void testNavigationProposalAfterRelaxedPropertyName() throws Exception {
IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app"); IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(p); useProject(p);
@@ -849,7 +852,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
assertCompletionsVariations("foo.colorData.red.na<*>", "foo.colorData.red.name=<*>"); assertCompletionsVariations("foo.colorData.red.na<*>", "foo.colorData.red.name=<*>");
} }
@Ignore @Test public void testValueProposalAssignedToRelaxedPropertyName() throws Exception { @Test public void testValueProposalAssignedToRelaxedPropertyName() throws Exception {
IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app"); IJavaProject p = createPredefinedMavenProject("enums-boot-1.3.2-app");
useProject(p); useProject(p);
@@ -983,7 +986,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testCharsetCompletions() throws Exception { @Test public void testCharsetCompletions() throws Exception {
data("foobar.encoding", "java.nio.charset.Charset", null, "The charset-encoding to use for foobars"); data("foobar.encoding", "java.nio.charset.Charset", null, "The charset-encoding to use for foobars");
assertCompletions( assertCompletions(
@@ -1001,7 +1004,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testLocaleCompletions() throws Exception { @Test public void testLocaleCompletions() throws Exception {
data("foobar.locale", "java.util.Locale", null, "Yada yada"); data("foobar.locale", "java.util.Locale", null, "Yada yada");
assertCompletions( assertCompletions(
@@ -1019,7 +1022,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testPropertyValueHintCompletions() throws Exception { @Test public void testPropertyValueHintCompletions() throws Exception {
//Test that 'value hints' work when property name is associated with 'value' hints. //Test that 'value hints' work when property name is associated with 'value' hints.
// via boot metadata. // via boot metadata.
@@ -1037,7 +1040,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testPropertyListHintCompletions() throws Exception { @Test public void testPropertyListHintCompletions() throws Exception {
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
assertCompletion( assertCompletion(
@@ -1065,7 +1068,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testPropertyMapValueCompletions() throws Exception { @Test public void testPropertyMapValueCompletions() throws Exception {
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
assertCompletionsDisplayString( assertCompletionsDisplayString(
@@ -1093,7 +1096,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testPropertyMapKeyCompletions() throws Exception { @Test public void testPropertyMapKeyCompletions() throws Exception {
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
assertCompletionWithLabel( assertCompletionWithLabel(
"logging.level.<*>" "logging.level.<*>"
@@ -1120,7 +1123,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testHandleAsResourceContentAssist() throws Exception { @Test public void testHandleAsResourceContentAssist() throws Exception {
//"name": "my.terms-and-conditions", //"name": "my.terms-and-conditions",
// "providers": [ // "providers": [
// { // {
@@ -1144,7 +1147,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testHandleAsListContentAssist() throws Exception { @Test public void testHandleAsListContentAssist() throws Exception {
data("my.tosses", "String[]", null, "A sequence of coin tosses") data("my.tosses", "String[]", null, "A sequence of coin tosses")
.provider("handle-as", "target", "java.lang.Boolean[]"); .provider("handle-as", "target", "java.lang.Boolean[]");
@@ -1179,7 +1182,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
} }
@Ignore @Test public void test_STS_3335_completions_list_nested_in_Map_of_String() throws Exception { @Test public void test_STS_3335_completions_list_nested_in_Map_of_String() throws Exception {
useProject(createPredefinedMavenProject("boot-1.3.3-sts-4335")); useProject(createPredefinedMavenProject("boot-1.3.3-sts-4335"));
assertCompletions( assertCompletions(
@@ -1201,7 +1204,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testSimpleResourceCompletion() throws Exception { @Test public void testSimpleResourceCompletion() throws Exception {
CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); CachingValueProvider.TIMEOUT = Duration.ofSeconds(20);
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
@@ -1225,7 +1228,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testClasspathResourceCompletion() throws Exception { @Test public void testClasspathResourceCompletion() throws Exception {
CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); CachingValueProvider.TIMEOUT = Duration.ofSeconds(20);
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
@@ -1295,7 +1298,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testClasspathResourceCompletionInCommaList() throws Exception { @Test public void testClasspathResourceCompletionInCommaList() throws Exception {
CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); CachingValueProvider.TIMEOUT = Duration.ofSeconds(20);
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
@@ -1490,7 +1493,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void test_PT_119352965() throws Exception { @Test public void test_PT_119352965() throws Exception {
data("some.property", "java.lang.String", null, "Some property to test stuff") data("some.property", "java.lang.String", null, "Some property to test stuff")
.valueHint("SOMETHING", "A value for something") .valueHint("SOMETHING", "A value for something")
.valueHint("ALTERNATE", "An alternative value"); .valueHint("ALTERNATE", "An alternative value");
@@ -1558,17 +1561,9 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
@Override @Override
protected SimpleLanguageServer newLanguageServer() { protected SimpleLanguageServer newLanguageServer() {
return new ApplicationPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider); ApplicationPropertiesLanguageServer server = new ApplicationPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder);
// return new ApplicationPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider) { server.setMaxCompletionsNumber(-1);
// return server;
// @Override
// protected IReconcileEngine getReconcileEngine() {
// SpringPropertiesReconcileEngine reconcileEngine = (SpringPropertiesReconcileEngine) super.getReconcileEngine();
// reconcileEngine.setRecordSyntaxErrors(false);
// return reconcileEngine;
// }
//
// };
} }
/** /**

View File

@@ -37,7 +37,7 @@ public class ApplicationPropertiesLanguageServerTest {
} }
private LanguageServerHarness newHarness() throws Exception { private LanguageServerHarness newHarness() throws Exception {
Callable<? extends LanguageServer> f = () -> new ApplicationPropertiesLanguageServer((d) -> null, (d) -> null); Callable<? extends LanguageServer> f = () -> new ApplicationPropertiesLanguageServer((d) -> null, (d) -> null, (d) -> null);
return new LanguageServerHarness(f); return new LanguageServerHarness(f);
} }

View File

@@ -27,6 +27,7 @@ public class ApplicationYamlLanguageServer extends SimpleLanguageServer {
private YamlASTProvider parser = new YamlParser(yaml); private YamlASTProvider parser = new YamlParser(yaml);
private SpringPropertyIndexProvider indexProvider; private SpringPropertyIndexProvider indexProvider;
private TypeUtilProvider typeUtilProvider; private TypeUtilProvider typeUtilProvider;
private VscodeCompletionEngineAdapter completionEngine;
public ApplicationYamlLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder javaProjectFinder) { public ApplicationYamlLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder javaProjectFinder) {
this.indexProvider = indexProvider; this.indexProvider = indexProvider;
@@ -57,10 +58,14 @@ public class ApplicationYamlLanguageServer extends SimpleLanguageServer {
typeUtilProvider, typeUtilProvider,
RelaxedNameConfig.COMPLETION_DEFAULTS RelaxedNameConfig.COMPLETION_DEFAULTS
); );
VscodeCompletionEngine completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine); completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine);
documents.onCompletion(completionEngine::getCompletions); documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion); documents.onCompletionResolve(completionEngine::resolveCompletion);
} }
public void setMaxCompletionsNumber(int number) {
completionEngine.setMaxCompletionsNumber(number);
}
protected IReconcileEngine getReconcileEngine() { protected IReconcileEngine getReconcileEngine() {
return new ApplicationYamlReconcileEngine(parser, indexProvider, typeUtilProvider); return new ApplicationYamlReconcileEngine(parser, indexProvider, typeUtilProvider);

View File

@@ -35,6 +35,7 @@ import org.springframework.ide.vscode.application.properties.metadata.util.Fuzzy
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match; import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.completion.LazyProposalApplier;
import org.springframework.ide.vscode.commons.languageserver.completion.ProposalApplier; import org.springframework.ide.vscode.commons.languageserver.completion.ProposalApplier;
import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;

View File

@@ -2987,7 +2987,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testPropertyMapKeyCompletions() throws Exception { @Test public void testPropertyMapKeyCompletions() throws Exception {
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
assertCompletionWithLabel( assertCompletionWithLabel(
"logging:\n" + "logging:\n" +
@@ -3090,7 +3090,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testSimpleResourceCompletion() throws Exception { @Test public void testSimpleResourceCompletion() throws Exception {
CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); CachingValueProvider.TIMEOUT = Duration.ofSeconds(20);
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
@@ -3134,7 +3134,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
} }
@Ignore @Test public void testClasspathResourceCompletion() throws Exception { @Test public void testClasspathResourceCompletion() throws Exception {
CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); CachingValueProvider.TIMEOUT = Duration.ofSeconds(20);
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app")); useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
@@ -3388,7 +3388,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testHandleAsResourceContentAssist() throws Exception { @Test public void testHandleAsResourceContentAssist() throws Exception {
//"name": "my.terms-and-conditions", //"name": "my.terms-and-conditions",
// "providers": [ // "providers": [
// { // {
@@ -3413,7 +3413,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
); );
} }
@Ignore @Test public void testBootBug5905() throws Exception { @Test public void testBootBug5905() throws Exception {
useProject(createPredefinedMavenProject("boot-1.3.3-app-with-resource-prop")); useProject(createPredefinedMavenProject("boot-1.3.3-app-with-resource-prop"));
//Check the metadata reflects the 'handle-as': //Check the metadata reflects the 'handle-as':
@@ -3542,7 +3542,9 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
@Override @Override
protected SimpleLanguageServer newLanguageServer() { protected SimpleLanguageServer newLanguageServer() {
return new ApplicationYamlLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder); ApplicationYamlLanguageServer server = new ApplicationYamlLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder);
server.setMaxCompletionsNumber(-1);
return server;
} }
} }