diff --git a/.gitignore b/.gitignore index e9a1f2ddf..cb58cf4c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ *.class -**/.classpath **/classpath.txt **/.idea **/*.iml diff --git a/eclipse-extensions/org.springframework.boot.ide.properties.editors/plugin.xml b/eclipse-extensions/org.springframework.boot.ide.properties.editors/plugin.xml index 0d5b5632d..3b2ef82ff 100644 --- a/eclipse-extensions/org.springframework.boot.ide.properties.editors/plugin.xml +++ b/eclipse-extensions/org.springframework.boot.ide.properties.editors/plugin.xml @@ -7,20 +7,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/CachingValueProvider.java b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/CachingValueProvider.java index c959dbaae..a3be8be31 100644 --- a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/CachingValueProvider.java +++ b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/CachingValueProvider.java @@ -11,8 +11,22 @@ package org.springframework.ide.vscode.application.properties.metadata; 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.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 @@ -53,74 +67,79 @@ public abstract class CachingValueProvider implements ValueProviderStrategy { */ private int MAX_RESULTS = 500; -// private Cache, CacheEntry> cache = createCache(); -// -// private class CacheEntry { -// boolean isComplete = false; -// int count = 0; -// Flux values; -// -// public CacheEntry(String query, Flux producer) { -// values = producer -// .take(MAX_RESULTS) -// .cache(MAX_RESULTS); -// values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max. -// } -// -// @Override -// public String toString() { -// return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]"; -// } -// -// } -// -// @Override -// public final Flux getValues(IJavaProject javaProject, String query) { -//// debug("CA query: "+query); -// Tuple2 key = key(javaProject, query); -// CacheEntry cached = cache.get(key); -// if (cached==null) { -// cache.put(key, cached = new CacheEntry(query, getValuesIncremental(javaProject, query))); -// } -// 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. -// *

-// * Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache. -// */ -// private Flux getValuesIncremental(IJavaProject javaProject, String query) { -//// debug("trying to solve "+query+" incrementally"); -// String subquery = query; -// while (subquery.length()>=1) { -// subquery = subquery.substring(0, subquery.length()-1); -// CacheEntry cached = cache.get(key(javaProject, subquery)); -// if (cached!=null) { -// System.out.println("cached "+subquery+": "+cached); -// if (cached.isComplete) { -//// debug("filtering "+subquery+" -> "+query); -// return cached.values -//// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue())) -// .filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString())); -// } else { -//// debug("subquery "+subquery+" cached but is incomplete"); -// } -// } -// } -//// debug("full search for: "+query); -// return getValuesAsycn(javaProject, query); -// } -// -// protected abstract Flux getValuesAsycn(IJavaProject javaProject, String query); -// -// private Tuple2 key(IJavaProject javaProject, String query) { -// return Tuples.of(javaProject==null?null:javaProject.getElementName(), query); -// } -// -// protected Cache createCache() { -// return new LimitedTimeCache<>(Duration.ofMinutes(1)); -// } + private Cache, CacheEntry> cache = createCache(); + + private class CacheEntry { + boolean isComplete = false; + int count = 0; + Flux values; + + public CacheEntry(String query, Flux producer) { + values = producer + .take(MAX_RESULTS) + .cache(MAX_RESULTS); + values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max. + } + + @Override + public String toString() { + return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]"; + } + + } + + @Override + public final Flux getValues(IJavaProject javaProject, String query) { + Tuple2 key = key(javaProject, query); + CacheEntry cached = null; + try { + cached = cache.get(key, () -> new CacheEntry(query, getValuesIncremental(javaProject, query))); + } catch (ExecutionException e) { + Log.log(e); + } + 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. + *

+ * Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache. + */ + private Flux getValuesIncremental(IJavaProject javaProject, String query) { +// debug("trying to solve "+query+" incrementally"); + String subquery = query; + while (subquery.length()>=1) { + subquery = subquery.substring(0, subquery.length()-1); + CacheEntry cached = null; + try { + cached = cache.get(key(javaProject, subquery), () -> null); + } catch (ExecutionException | InvalidCacheLoadException e) { +// Log.log(e); + } + if (cached!=null) { + System.out.println("cached "+subquery+": "+cached); + if (cached.isComplete) { + return cached.values +// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue())) + .filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString())); + } else { +// debug("subquery "+subquery+" cached but is incomplete"); + } + } + } +// debug("full search for: "+query); + return getValuesAsycn(javaProject, query); + } + + protected abstract Flux getValuesAsycn(IJavaProject javaProject, String query); + + private Tuple2 key(IJavaProject javaProject, String query) { + return Tuples.of(javaProject==null?null:javaProject.getElementName(), query); + } + + protected Cache createCache() { + return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(); + } public static void restoreDefaults() { TIMEOUT = DEFAULT_TIMEOUT; diff --git a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/ResourceHintProvider.java b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/ResourceHintProvider.java new file mode 100644 index 000000000..3200b2a01 --- /dev/null +++ b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/ResourceHintProvider.java @@ -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 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 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 getValuesAsycn(IJavaProject javaProject, String query) { + return Flux.fromStream(javaProject.getClasspath().getClasspathResources().distinct().map(StsValueHint::create)); + } + } + + +} diff --git a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/completions/PropertyCompletionFactory.java b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/completions/PropertyCompletionFactory.java index 360aedab3..87c75fde5 100644 --- a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/completions/PropertyCompletionFactory.java +++ b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/completions/PropertyCompletionFactory.java @@ -97,6 +97,11 @@ public class PropertyCompletionFactory { return typeUtil.niceTypeName((Type) type); } + @Override + public String getLabel() { + return getBaseDisplayString() + " : " + typeUtil.niceTypeName(getType()); + } + }; if (property.isDeprecated()) { proposal.deprecate(); @@ -157,6 +162,12 @@ public class PropertyCompletionFactory { protected String niceTypeName(YType type) { return typeUtil.niceTypeName(((Type)type)); } + + @Override + public String getLabel() { + return getBaseDisplayString() + " : " + typeUtil.niceTypeName(getType()); + } + } } diff --git a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/types/TypeUtil.java b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/types/TypeUtil.java index 62a6950ee..e538f7519 100644 --- a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/types/TypeUtil.java +++ b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/types/TypeUtil.java @@ -26,6 +26,7 @@ import java.util.stream.Stream; import javax.inject.Provider; 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.hints.StsValueHint; import org.springframework.ide.vscode.application.properties.metadata.util.DeprecationUtil; @@ -577,7 +578,7 @@ public class TypeUtil { return null; } }); -// valueHints("org.springframework.core.io.Resource", new ResourceHintProvider()); + valueHints("org.springframework.core.io.Resource", new ResourceHintProvider()); } /** diff --git a/vscode-extensions/commons/application-properties-metadata/src/test/java/org/springframework/ide/vscode/boot/properties/metadata/PropertiesMetadataTestData.java b/vscode-extensions/commons/application-properties-metadata/src/test/java/org/springframework/ide/vscode/boot/properties/metadata/PropertiesMetadataTestData.java index d12569ed3..94d366581 100644 --- a/vscode-extensions/commons/application-properties-metadata/src/test/java/org/springframework/ide/vscode/boot/properties/metadata/PropertiesMetadataTestData.java +++ b/vscode-extensions/commons/application-properties-metadata/src/test/java/org/springframework/ide/vscode/boot/properties/metadata/PropertiesMetadataTestData.java @@ -90,6 +90,12 @@ public class PropertiesMetadataTestData { public Stream getClasspathEntries() throws Exception { return Stream.empty(); } + + @Override + public Stream getClasspathResources() { + return Stream.empty(); + } + }); for (ConfigurationMetadataProperty propertyInfo : datas.values()) { index.add(propertyInfo); diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java index 8ec628f0c..333d897ff 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java @@ -16,6 +16,7 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.PrimitiveType; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; +import org.springframework.ide.vscode.commons.java.Flags; import org.springframework.ide.vscode.commons.java.IAnnotation; import org.springframework.ide.vscode.commons.java.IField; import org.springframework.ide.vscode.commons.java.IJavaType; @@ -28,8 +29,6 @@ import org.springframework.ide.vscode.commons.util.HtmlSnippet; public class Wrappers { - private static final int AccEnum = 0x4000; - public static IType wrap(IndexView index, ClassInfo info) { if (info == null) { return null; @@ -75,7 +74,7 @@ public class Wrappers { @Override public boolean isEnum() { - return (info.flags() & AccEnum) != 0; + return Flags.isEnum(info.flags()); } @Override @@ -161,7 +160,7 @@ public class Wrappers { @Override public boolean isEnumConstant() { - return (field.flags() & AccEnum) != 0; + return Flags.isEnum(field.flags()); } @Override diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IClasspath.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IClasspath.java index 5f4f1dbd6..81ef98d3a 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IClasspath.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IClasspath.java @@ -30,4 +30,10 @@ public interface IClasspath { */ Stream getClasspathEntries() throws Exception; + /** + * Classpath resources paths relative to the source folder path + * @return classpath resource relative paths + */ + Stream getClasspathResources(); + } diff --git a/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/LazyProposalApplier.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/LazyProposalApplier.java similarity index 72% rename from vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/LazyProposalApplier.java rename to vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/LazyProposalApplier.java index fc655926e..85be1d905 100644 --- a/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/LazyProposalApplier.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/LazyProposalApplier.java @@ -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 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. * 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. */ public class LazyProposalApplier { - + public static DocumentEdits from(Callable createEdits) throws Exception { return createEdits.call(); } diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java index afc487c0d..8e62f6268 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java @@ -27,6 +27,8 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { private final static int MAX_COMPLETIONS = 20; + private int maxCompletions = MAX_COMPLETIONS; + final static Logger logger = LoggerFactory.getLogger(VscodeCompletionEngineAdapter.class); public static final String VS_CODE_CURSOR_MARKER = "{{}}"; @@ -39,6 +41,10 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { this.engine = engine; } + public void setMaxCompletionsNumber(int maxCompletions) { + this.maxCompletions = maxCompletions; + } + @Override public CompletableFuture getCompletions(TextDocumentPositionParams params) { //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; for (ICompletionProposal c : completions) { count++; - if (count>MAX_COMPLETIONS) { + if (maxCompletions > 0 && count>maxCompletions) { list.setIsIncomplete(true); break; } diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/DefaultJavaProjectFinder.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/DefaultJavaProjectFinder.java index 8503c514d..e4257b169 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/DefaultJavaProjectFinder.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/DefaultJavaProjectFinder.java @@ -7,8 +7,8 @@ import org.springframework.ide.vscode.commons.util.Log; public class DefaultJavaProjectFinder implements JavaProjectFinder { private final IJavaProjectFinderStrategy[] STRATEGIES = new IJavaProjectFinderStrategy[] { - new JavaProjectWithClasspathFileFinderStrategy(), - new MavenProjectFinderStrategy() + new MavenProjectFinderStrategy(), + new JavaProjectWithClasspathFileFinderStrategy() }; @Override diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/JavaProjectWithClasspathFileFinderStrategy.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/JavaProjectWithClasspathFileFinderStrategy.java index 9851272d0..2227eaf51 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/JavaProjectWithClasspathFileFinderStrategy.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/JavaProjectWithClasspathFileFinderStrategy.java @@ -35,12 +35,14 @@ public class JavaProjectWithClasspathFileFinderStrategy implements IJavaProjectF URI uri = new URI(uriStr); // TODO: This only work with File uri. Should it work with others // too? - File file = toFile(uri); - File cpFile = FileUtils.findFile(file, MavenCore.CLASSPATH_TXT); - if (cpFile != null) { - return cache.get(cpFile, () -> { - return new JavaProjectWithClasspathFile(cpFile); - }); + if (uri.getScheme().equalsIgnoreCase("file")) { + File file = toFile(uri); + File cpFile = FileUtils.findFile(file, MavenCore.CLASSPATH_TXT); + if (cpFile != null) { + return cache.get(cpFile, () -> { + return new JavaProjectWithClasspathFile(cpFile); + }); + } } } return null; diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/MavenProjectFinderStrategy.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/MavenProjectFinderStrategy.java index 8f5c85429..50e74d57b 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/MavenProjectFinderStrategy.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/MavenProjectFinderStrategy.java @@ -41,12 +41,14 @@ public class MavenProjectFinderStrategy implements IJavaProjectFinderStrategy { URI uri = new URI(uriStr); // TODO: This only work with File uri. Should it work with others // too? - File file = new File(uri).getAbsoluteFile(); - File pomFile = FileUtils.findFile(file, MavenCore.POM_XML); - if (pomFile != null) { - return cache.get(pomFile, () -> { - return new MavenJavaProject(pomFile); - }); + if (uri.getScheme().equalsIgnoreCase("file")) { + File file = new File(uri).getAbsoluteFile(); + File pomFile = FileUtils.findFile(file, MavenCore.POM_XML); + if (pomFile != null) { + return cache.get(pomFile, () -> { + return new MavenJavaProject(pomFile); + }); + } } } return null; diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index 1afe6bb6d..f76fdf3e9 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -182,80 +182,66 @@ public class SimpleTextDocumentService implements TextDocumentService { @Override public CompletableFuture hover(TextDocumentPositionParams position) { - // TODO Auto-generated method stub - return null; + return Futures.of(null); } @Override public CompletableFuture signatureHelp(TextDocumentPositionParams position) { - // TODO Auto-generated method stub - return null; + return Futures.of(null); } @Override public CompletableFuture> definition(TextDocumentPositionParams position) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } @Override public CompletableFuture> references(ReferenceParams params) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } @Override public CompletableFuture> documentSymbol(DocumentSymbolParams params) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } @Override public CompletableFuture> codeAction(CodeActionParams params) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } @Override public CompletableFuture> codeLens(CodeLensParams params) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } @Override public CompletableFuture resolveCodeLens(CodeLens unresolved) { - // TODO Auto-generated method stub - return null; + return Futures.of(null); } @Override public CompletableFuture> formatting(DocumentFormattingParams params) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } @Override public CompletableFuture> rangeFormatting(DocumentRangeFormattingParams params) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } @Override public CompletableFuture> onTypeFormatting(DocumentOnTypeFormattingParams params) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } @Override public CompletableFuture rename(RenameParams params) { - // TODO Auto-generated method stub - return null; + return Futures.of(null); } @Override public void didSave(DidSaveTextDocumentParams params) { - // TODO Auto-generated method stub - } public void publishDiagnostics(TextDocument doc, List diagnostics) { @@ -274,8 +260,7 @@ public class SimpleTextDocumentService implements TextDocumentService { @Override public CompletableFuture> documentHighlight(TextDocumentPositionParams position) { - // TODO Auto-generated method stub - return null; + return Futures.of(Collections.emptyList()); } } diff --git a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java index 074f59a5a..6d18e39d8 100644 --- a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java +++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java @@ -13,9 +13,11 @@ package org.springframework.ide.vscode.commons.maven.java; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.stream.Stream; 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.java.IClasspath; import org.springframework.ide.vscode.commons.java.IType; @@ -70,5 +72,22 @@ public class MavenProjectClasspath implements IClasspath { private File findIndexFile(File jarFile) { return new File(maven.getIndexFolder().toString(), jarFile.getName() + "-" + jarFile.lastModified() + ".jdx"); } - + + @Override + public Stream 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()); + }); + } + } diff --git a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/FileClasspath.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/FileClasspath.java index 732d4c45a..c71005ded 100644 --- a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/FileClasspath.java +++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/FileClasspath.java @@ -37,4 +37,9 @@ public class FileClasspath implements IClasspath { classpathFilePath.getParent().resolve("target/test-classes"))); } + @Override + public Stream getClasspathResources() { + return Stream.empty(); + } + } diff --git a/vscode-extensions/commons/java-properties/resources/JavaProperties.g4 b/vscode-extensions/commons/java-properties/resources/JavaProperties.g4 index 3ebd00c38..8407fb934 100644 --- a/vscode-extensions/commons/java-properties/resources/JavaProperties.g4 +++ b/vscode-extensions/commons/java-properties/resources/JavaProperties.g4 @@ -52,7 +52,7 @@ keyChar ; separatorAndValue - : (Space | Colon | Equals) valueChar+ + : (Space | Colon | Equals) valueChar* ; valueChar diff --git a/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/antlr/parser/AntlrParser.java b/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/antlr/parser/AntlrParser.java index 579eb0772..1fecb4e1a 100644 --- a/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/antlr/parser/AntlrParser.java +++ b/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/antlr/parser/AntlrParser.java @@ -12,10 +12,12 @@ package org.springframework.ide.vscode.java.properties.antlr.parser; import java.util.ArrayList; import java.util.BitSet; +import java.util.List; import org.antlr.v4.runtime.ANTLRErrorListener; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ConsoleErrorListener; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.RecognitionException; 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.dfa.DFA; 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.PropertyLineContext; 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); JavaPropertiesParser parser = new JavaPropertiesParser(tokens); + // To avoid printing parse errors in the console + parser.removeErrorListener(ConsoleErrorListener.INSTANCE); + // Add listener to collect various parser errors parser.addErrorListener(new ANTLRErrorListener() { @@ -90,7 +96,9 @@ public class AntlrParser implements Parser { @Override 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; value = null; } @@ -110,8 +118,13 @@ public class AntlrParser implements Parser { value = new Value(ctx); } + @Override + public void exitEmptyLine(EmptyLineContext ctx) { + astNodes.add(new EmptyLine(ctx)); + } + }); - + parser.parse(); // Collect and return parse results @@ -156,6 +169,9 @@ public class AntlrParser implements Parser { private static abstract class Node implements PropertiesAst.Node { + Node parent; + List children; + abstract protected ParserRuleContext getContext(); @Override @@ -167,14 +183,40 @@ public class AntlrParser implements Parser { public int getLength() { return getContext().getStop().getStartIndex() - getOffset() + 1; } + + @Override + public Node getParent() { + return parent; + } + + @Override + public List 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; public Comment(CommentLineContext context) { + super(); this.context = context; } @@ -190,6 +232,11 @@ public class AntlrParser implements Parser { public int getLength() { return context.getStop().getStartIndex() - getOffset() + 1; } + + @Override + protected CommentLineContext getContext() { + return context; + } } @@ -200,9 +247,11 @@ public class AntlrParser implements Parser { private Value value; public KeyValuePair(PropertyLineContext context, Key key, Value value) { + super(); this.context = context; this.key = key; this.value = value; + this.children = ImmutableList.of(key, value); } protected PropertyLineContext getContext() { @@ -218,6 +267,18 @@ public class AntlrParser implements Parser { public Value getValue() { 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 { @@ -241,6 +302,11 @@ public class AntlrParser implements Parser { 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() { // 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 - decoded = value.replaceAll("\\\\(\r?\n|\r)[ \t\f]*", ""); + decoded = value.replaceAll("^\\s*", "").replaceAll("\\\\(\r?\n|\r)[ \t\f]*", ""); try { decoded = PropertiesFileEscapes.unescape(decoded); } catch (Exception e) { @@ -283,6 +349,10 @@ public class AntlrParser implements Parser { return context.getStart().getStartIndex() + (context.getText().length() - value.length()); } + @Override + public KeyValuePair getParent() { + return (KeyValuePair) super.getParent(); + } } } diff --git a/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/antlr/parser/JavaPropertiesParser.java b/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/antlr/parser/JavaPropertiesParser.java index 486720d91..29d083bea 100644 --- a/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/antlr/parser/JavaPropertiesParser.java +++ b/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/antlr/parser/JavaPropertiesParser.java @@ -623,20 +623,20 @@ public class JavaPropertiesParser extends Parser { } else { consume(); } - setState(80); + setState(82); _errHandler.sync(this); _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); valueChar(); } } - setState(82); + setState(84); _errHandler.sync(this); _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) { @@ -677,56 +677,56 @@ public class JavaPropertiesParser extends Parser { ValueCharContext _localctx = new ValueCharContext(_ctx, getState()); enterRule(_localctx, 18, RULE_valueChar); try { - setState(92); + setState(93); switch (_input.LA(1)) { case IdentifierChar: enterOuterAlt(_localctx, 1); { - setState(84); + setState(85); match(IdentifierChar); } break; case Exclamation: enterOuterAlt(_localctx, 2); { - setState(85); + setState(86); match(Exclamation); } break; case Number: enterOuterAlt(_localctx, 3); { - setState(86); + setState(87); match(Number); } break; case Space: enterOuterAlt(_localctx, 4); { - setState(87); + setState(88); match(Space); } break; case Backslash: enterOuterAlt(_localctx, 5); { - setState(88); - match(Backslash); setState(89); + match(Backslash); + setState(90); match(LineBreak); } break; case Equals: enterOuterAlt(_localctx, 6); { - setState(90); + setState(91); match(Equals); } break; case Colon: enterOuterAlt(_localctx, 7); { - setState(91); + setState(92); match(Colon); } break; @@ -746,31 +746,31 @@ public class JavaPropertiesParser extends Parser { } 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"+ "\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"+ "\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"+ - "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"+ - "\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"+ - "\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"+ - "\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"+ - "\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"+ - "\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 \""+ - "\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"+ - "\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"+ - "\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"+ - "\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"+ - "\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:<\7\t\2\2;:\3\2\2\2\3\2\2\2>@\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\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\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\2PR\t\6\2\2Q"+ - "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\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"+ - "\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^]"+ - "\3\2\2\2_\25\3\2\2\2\f\31!&.\65=INT^"; + "\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\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"+ + "\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"+ + "\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"+ + "\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"+ + "\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"+ + "\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\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$#\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"+ + "\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"+ + "\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"+ + "\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"+ + "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@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\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\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"+ + "\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"+ + "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"+ + "\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"+ + "\2_]\3\2\2\2_^\3\2\2\2`\25\3\2\2\2\f\31!&.\65=INT_"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/parser/PropertiesAst.java b/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/parser/PropertiesAst.java index 0f8f7830e..3fb65b328 100644 --- a/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/parser/PropertiesAst.java +++ b/vscode-extensions/commons/java-properties/src/main/java/org/springframework/ide/vscode/java/properties/parser/PropertiesAst.java @@ -31,7 +31,7 @@ public final class PropertiesAst { * Retrieves all AST nodes * @return List of AST nodes sorted by line number */ - public List getAllNodes() { + public List getAllNodes() { return nodes; } @@ -48,6 +48,52 @@ public final class PropertiesAst { return (List) 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 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 */ @@ -65,6 +111,18 @@ public final class PropertiesAst { */ int getLength(); + /** + * Node's parent + * @return parent node + */ + Node getParent(); + + /** + * Node's children + * @return children nodes + */ + List getChildren(); + } /** @@ -72,6 +130,13 @@ public final class PropertiesAst { */ 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 */ String decode(); + + KeyValuePair getParent(); } @@ -117,6 +184,7 @@ public final class PropertiesAst { */ String decode(); + KeyValuePair getParent(); } } diff --git a/vscode-extensions/commons/java-properties/src/test/java/org/springframework/ide/vscode/java/properties/parser/test/PropertiesAntlrParserTest.java b/vscode-extensions/commons/java-properties/src/test/java/org/springframework/ide/vscode/java/properties/parser/test/PropertiesAntlrParserTest.java index 18ceb84e5..ca1247a8a 100644 --- a/vscode-extensions/commons/java-properties/src/test/java/org/springframework/ide/vscode/java/properties/parser/test/PropertiesAntlrParserTest.java +++ b/vscode-extensions/commons/java-properties/src/test/java/org/springframework/ide/vscode/java/properties/parser/test/PropertiesAntlrParserTest.java @@ -83,34 +83,6 @@ public class PropertiesAntlrParserTest { 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 public void testPropertyWithEqualsSeparator() throws Exception { testPropertyLine("key=value", "key", "key", "value", "value"); @@ -118,7 +90,7 @@ public class PropertiesAntlrParserTest { @Test 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 @@ -128,7 +100,7 @@ public class PropertiesAntlrParserTest { @Test 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 @@ -168,17 +140,17 @@ public class PropertiesAntlrParserTest { @Test public void testEqualsValueSeparatedWithEqualsAndSpace() throws Exception { - testPropertyLine("key7 = =", "key7", "key7", "=", "="); + testPropertyLine("key7 = =", "key7", "key7", "=", " ="); } @Test 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 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 diff --git a/vscode-extensions/commons/java-properties/src/test/java/org/springframework/ide/vscode/java/properties/parser/test/PropertiesAstTest.java b/vscode-extensions/commons/java-properties/src/test/java/org/springframework/ide/vscode/java/properties/parser/test/PropertiesAstTest.java new file mode 100644 index 000000000..aeb07ea22 --- /dev/null +++ b/vscode-extensions/commons/java-properties/src/test/java/org/springframework/ide/vscode/java/properties/parser/test/PropertiesAstTest.java @@ -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()); + } + +} diff --git a/vscode-extensions/commons/project-test-harness/.classpath b/vscode-extensions/commons/project-test-harness/.classpath new file mode 100644 index 000000000..8620f6a2b --- /dev/null +++ b/vscode-extensions/commons/project-test-harness/.classpath @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vscode-extensions/commons/project-test-harness/.gitignore b/vscode-extensions/commons/project-test-harness/.gitignore index 36dc59cb6..ca0618fbd 100644 --- a/vscode-extensions/commons/project-test-harness/.gitignore +++ b/vscode-extensions/commons/project-test-harness/.gitignore @@ -1,5 +1,5 @@ -/.classpath /.project +**/.classpath **/classpath.txt **/.project **/.settings/*.* diff --git a/vscode-extensions/commons/project-test-harness/pom.xml b/vscode-extensions/commons/project-test-harness/pom.xml index 75b41d4fd..6706409e1 100644 --- a/vscode-extensions/commons/project-test-harness/pom.xml +++ b/vscode-extensions/commons/project-test-harness/pom.xml @@ -37,4 +37,11 @@ ${junit-version} + + + + test-projects + + + \ No newline at end of file diff --git a/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java b/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java index 02b9efb6b..81c7ba423 100644 --- a/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java +++ b/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java @@ -10,10 +10,21 @@ *******************************************************************************/ package org.springframework.ide.vscode.project.harness; +import java.io.File; +import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; +import java.nio.file.CopyOption; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.maven.MavenCore; @@ -33,7 +44,7 @@ public class ProjectsHarness { public static final ProjectsHarness INSTANCE = new ProjectsHarness();; - public Cache cache = CacheBuilder.newBuilder().build(); + public Cache cache = CacheBuilder.newBuilder().concurrencyLevel(1).build(); private enum ProjectType { MAVEN, @@ -59,10 +70,94 @@ public class ProjectsHarness { }); } - protected Path getProjectPath(String name) throws URISyntaxException { - URL sourceLocation = ProjectsHarness.class.getProtectionDomain().getCodeSource().getLocation(); + protected Path getProjectPath(String name) throws URISyntaxException, IOException { + URI sourceLocation = ProjectsHarness.class.getProtectionDomain().getCodeSource().getLocation().toURI(); // file:/Users/aboyko/git/sts4/vscode-extensions/commons/project-test-harness/target/project-test-harness-0.0.1-SNAPSHOT.jar - return Paths.get(sourceLocation.toURI()).getParent().getParent().resolve("test-projects").resolve(name); + Path testProjectsPath = Paths.get(sourceLocation).getParent().getParent().resolve("test-projects").resolve(name); + if (Files.exists(testProjectsPath)) { + return testProjectsPath; + } else { + /* + * If "test-projects" folder is not found then extract test project + * from the jar's "test-projects" folder and copy it in the temp + * folder + */ + return getProjectPathFromClasspath(name); + } + } + + private Path getProjectPathFromClasspath(String name) throws URISyntaxException, IOException { + URI resource = ProjectsHarness.class.getResource("/" + name).toURI(); + if (resource.getScheme().equalsIgnoreCase("jar")) { + return getProjectPathFromJar(resource); + } else { + return Paths.get(resource); + } + } + + private Path getProjectPathFromJar(URI jar) throws IOException { + final String[] array = jar.toString().split("!"); + URI firstHalf = URI.create(array[0]); + Path tempFolderPath = Paths.get(new File(System.getProperty(MavenCore.JAVA_IO_TMPDIR)).toURI()); + FileSystem fs = FileSystems.newFileSystem(firstHalf, Collections.emptyMap()); + try { + Path path = fs.getPath(array[1]); + Path projectCopyPath = tempFolderPath.resolve(path.getFileName().toString()); + if (Files.exists(projectCopyPath)) { + recursiveDelete(projectCopyPath); + } + recursiveCopy(path, tempFolderPath, StandardCopyOption.REPLACE_EXISTING); + System.out.println("Copied test project to: " + projectCopyPath); + return projectCopyPath; + } finally { + fs.close(); + } + } + + private static void recursiveCopy(Path source, Path target, CopyOption... options) throws IOException { + Files.walkFileTree(source, new SimpleFileVisitor() { + + Path destination = target; + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + destination = destination.resolve(dir.getFileName().toString()); + Files.copy(dir, destination, options); + return super.preVisitDirectory(dir, attrs); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path newFile = destination.resolve(file.getFileName().toString()); + Files.copy(file, newFile, options); + return super.visitFile(file, attrs); + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + destination = destination.getParent(); + return super.postVisitDirectory(dir, exc); + } + + }); + } + + private static void recursiveDelete(Path path) throws IOException { + Files.walkFileTree(path, new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return super.visitFile(file, attrs); + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return super.postVisitDirectory(dir, exc); + } + + }); } public MavenJavaProject mavenProject(String name) throws Exception { diff --git a/vscode-extensions/commons/properties-editor-test-harness/.classpath b/vscode-extensions/commons/properties-editor-test-harness/.classpath new file mode 100644 index 000000000..af1430be1 --- /dev/null +++ b/vscode-extensions/commons/properties-editor-test-harness/.classpath @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vscode-extensions/vscode-application-properties/lib/Main.ts b/vscode-extensions/vscode-application-properties/lib/Main.ts index 68c369e38..9a6140921 100644 --- a/vscode-extensions/vscode-application-properties/lib/Main.ts +++ b/vscode-extensions/vscode-application-properties/lib/Main.ts @@ -49,7 +49,8 @@ export function activate(context: VSCode.ExtensionContext) { // TODO: Remove cast ones https://github.com/Microsoft/vscode-languageserver-node/issues/9 is resolved documentSelector: [ {language: 'ini', pattern: '**/application*.properties'}, - {language: 'java-properties', pattern: '**/application*.properties'} + {language: 'java-properties', pattern: '**/application*.properties'}, + {language: 'properties', pattern: '**/application*.properties'} ], synchronize: { // Synchronize the setting section to the server: diff --git a/vscode-extensions/vscode-application-properties/package.json b/vscode-extensions/vscode-application-properties/package.json index 5306985ca..86779d2bc 100644 --- a/vscode-extensions/vscode-application-properties/package.json +++ b/vscode-extensions/vscode-application-properties/package.json @@ -22,7 +22,8 @@ ], "activationEvents": [ "onLanguage:ini", - "onLanguage:java-properties" + "onLanguage:java-properties", + "onLanguage:properties" ], "main": "./out/lib/Main", "files": [ diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java index 7a57f8e5d..55814e5f1 100644 --- a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java @@ -10,12 +10,15 @@ *******************************************************************************/ package org.springframework.ide.vscode.application.properties; +import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentSyncKind; +import org.springframework.ide.vscode.application.properties.completions.SpringPropertiesCompletionEngine; import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider; import org.springframework.ide.vscode.application.properties.reconcile.SpringPropertiesReconcileEngine; -import org.springframework.ide.vscode.commons.languageserver.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.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.languageserver.util.TextDocument; @@ -30,18 +33,39 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer { private SpringPropertyIndexProvider indexProvider; 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.typeUtilProvider = typeUtilProvider; SimpleTextDocumentService documents = getTextDocumentService(); - IReconcileEngine reconcileEngine = getReconcileEngine(); + reconcileEngine = getReconcileEngine(); documents.onDidChangeContent(params -> { TextDocument doc = params.getDocument(); validateWith(doc, reconcileEngine); }); + + SpringPropertiesCompletionEngine propertiesCompletionEngine = new SpringPropertiesCompletionEngine( + indexProvider, + typeUtilProvider, + javaProjectFinder + ); + completionEngine = new VscodeCompletionEngineAdapter(this, propertiesCompletionEngine); + completionEngine.setMaxCompletionsNumber(-1); + 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 @@ -50,10 +74,14 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer { c.setTextDocumentSync(TextDocumentSyncKind.Full); + CompletionOptions completionProvider = new CompletionOptions(); + completionProvider.setResolveProvider(false); + c.setCompletionProvider(completionProvider); + return c; } - protected IReconcileEngine getReconcileEngine() { + protected SpringPropertiesReconcileEngine getReconcileEngine() { return new SpringPropertiesReconcileEngine(indexProvider, typeUtilProvider); } diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/Main.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/Main.java index 0ccbe37f1..b30f7fc22 100644 --- a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/Main.java +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/Main.java @@ -11,7 +11,6 @@ package org.springframework.ide.vscode.application.properties; import java.io.IOException; -import java.util.logging.Logger; import org.eclipse.lsp4j.services.LanguageServer; import org.springframework.ide.vscode.application.properties.metadata.DefaultSpringPropertyIndexProvider; @@ -36,7 +35,7 @@ public class Main { JavaProjectFinder javaProjectFinder = JavaProjectFinder.DEFAULT; SpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder); TypeUtilProvider typeUtilProvider = (IDocument doc) -> new TypeUtil(javaProjectFinder.find(doc)); - LanguageServer server = new ApplicationPropertiesLanguageServer(indexProvider, typeUtilProvider); + LanguageServer server = new ApplicationPropertiesLanguageServer(indexProvider, typeUtilProvider, javaProjectFinder); return server; }); } diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/PropertiesCompletionProposalsCalculator.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/PropertiesCompletionProposalsCalculator.java new file mode 100644 index 000000000..c267e465e --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/PropertiesCompletionProposalsCalculator.java @@ -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 index; + private TypeUtil typeUtil; + private PropertyCompletionFactory completionFactory; + private IDocument doc; + private int offset; + private boolean preferLowerCaseEnums; + private AntlrParser parser; + + PropertiesCompletionProposalsCalculator(FuzzyMap 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 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 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 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 getKeyHintProposals(PropertyInfo prop, int navOffset) { + HintProvider hintProvider = prop.getHints(typeUtil, false); + if (!HintProviders.isNull(hintProvider)) { + String query = textBetween(doc, navOffset+1, offset); + List 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 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 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 createPropertyProposals(Type type, int navOffset, + String prefix, List objectProperties) { + ArrayList proposals = new ArrayList(); + 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 pairs = parser.parse(doc.get()).ast.getNodes(KeyValuePair.class); +// return findPair(pairs, offset, 0, pairs.size() - 1); +// } +// +// private KeyValuePair findPair(List pairs, int offset, int start, int end) { +// if (start == end) { +// KeyValuePair pair = pairs.get(start); +// if (pair.getOffset() <= offset && offset <= pair.getOffset() + pair.getLength()) { +// return pair; +// } else { +// return null; +// } +// } else if (start < end ) { +// int pivotIndex = (start + end) / 2; +// KeyValuePair pair = pairs.get(pivotIndex); +// if (pair.getOffset() > offset) { +// return findPair(pairs, offset, start, pivotIndex - 1); +// } else if (offset > pair.getOffset() + pair.getLength()) { +// return findPair(pairs, offset, pivotIndex + 1, end); +// } else { +// return pair; +// } +// } else { +// return null; +// } +// } + +// private HoverInfo getValueHoverInfo(DocumentRegion value) { +// try { +// String valueString = value.toString(); +// IDocument doc = value.getDocument(); +// ITypedRegion valuePartition = getPartition(value.getDocument(), value.getStart()); +// int valuePartitionStart = valuePartition.getOffset(); +// String propertyName = fuzzySearchPrefix.getPrefix(doc, valuePartitionStart); //note: no need to skip whitespace backwards. +// //because value partition includes whitespace around the assignment +// +// Type type = getValueType(propertyName); +// if (TypeUtil.isArray(type) || TypeUtil.isList(type)) { +// //It is useful to provide content assist for the values in the list when entering a list +// type = TypeUtil.getDomainType(type); +// } +// if (TypeUtil.isClass(type)) { +// //Special case. We want to provide hoverinfos more liberally than what's suggested for completions (i.e. even class names +// //that are not suggested by the hints because they do not meet subtyping constraints should be hoverable and linkable! +// StsValueHint hint = StsValueHint.className(valueString, typeUtil); +// if (hint!=null) { +// return new ValueHintHoverInfo(hint); +// } +// } +// //Hack: pretend to invoke content-assist at the end of the value text. This should provide hints applicable to that value +// // then show hoverinfo based on that. That way we can avoid duplication a lot of similar logic to compute hoverinfos and hyperlinks. +// Collection hints = getValueHints(valueString, propertyName, EnumCaseMode.ALIASED); +// if (hints!=null) { +// for (StsValueHint h : hints) { +// if (valueString.equals(h.getValue())) { +// return new ValueHintHoverInfo(h); +// } +// } +// } +// } catch (BadLocationException e) { +// Log.log(e); +// } +// return null; +// } + + private Collection getValueCompletions(Value value) { + DocumentRegion valueRegion = createRegion(doc, value).trimStart(SPACES).trimEnd(SPACES); + String query = valuePrefixFinder.getPrefix(doc, offset, valueRegion.getStart()); + 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 valueCompletions = getValueHints(query, propertyName, caseMode); + if (valueCompletions != null && !valueCompletions.isEmpty()) { + ArrayList proposals = new ArrayList(); + 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 getValueHints(String query, String propertyName, EnumCaseMode caseMode) { + Type type = getValueType(propertyName); + if (TypeUtil.isArray(type) || TypeUtil.isList(type)) { + //It is useful to provide content assist for the values in the list when entering a list + type = TypeUtil.getDomainType(type); + } + List allHints = new ArrayList<>(); + { + Collection hints = typeUtil.getHintValues(type, query, caseMode); + if (CollectionUtil.hasElements(hints)) { + allHints.addAll(hints); + } + } + { + PropertyInfo prop = index.findLongestCommonPrefixEntry(propertyName); + if (prop!=null) { + HintProvider hintProvider = prop.getHints(typeUtil, false); + if (!HintProviders.isNull(hintProvider)) { + allHints.addAll(hintProvider.getValueHints(query)); + } + } + } + return allHints; + } + + /** + * Determine the value type for a give propertyName. + */ + protected Type getValueType(String propertyName) { + try { + PropertyInfo prop = index.get(propertyName); + if (prop!=null) { + return TypeParser.parse(prop.getType()); + } else { + prop = findLongestValidProperty(index, propertyName); + if (prop!=null) { + TextDocument doc = new TextDocument(null); + doc.setText(propertyName); + PropertyNavigator navigator = new PropertyNavigator(doc, null, typeUtil, new DocumentRegion(doc, 0, doc.getLength())); + return navigator.navigate(prop.getId().length(), TypeParser.parse(prop.getType())); + } + } + } catch (Exception e) { + Log.log(e); + } + return null; + } + + private List> findMatches(String prefix) { + List> matches = index.find(camelCaseToHyphens(prefix)); + return matches; + } + + private Collection getPropertyCompletions() throws BadLocationException { + Collection navProposals = getNavigationProposals(); + if (!navProposals.isEmpty()) { + return navProposals; + } + return getFuzzyCompletions(); + } + + protected Collection getFuzzyCompletions() { + final String prefix = fuzzySearchPrefix.getPrefix(doc, offset); + if (prefix != null) { + Collection> matches = findMatches(prefix); + if (matches!=null && !matches.isEmpty()) { + ArrayList proposals = new ArrayList(matches.size()); + for (final Match 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 (endbestCommonPrefixLen // it has a longer common prefix +// || commonPrefixLen==bestCommonPrefixLen && extraLen index, String name) { + int bracketPos = name.indexOf('['); + int endPos = bracketPos>=0?bracketPos:name.length(); + PropertyInfo prop = null; + String prefix = null; + while (endPos>0 && prop==null) { + prefix = name.substring(0, endPos); + String canonicalPrefix = camelCaseToHyphens(prefix); + prop = index.get(canonicalPrefix); + if (prop==null) { + endPos = name.lastIndexOf('.', endPos-1); + } + } + if (prop!=null) { + //We should meet caller's expectation that matched properties returned by this method + // match the names exactly even if we found them using relaxed name matching. + return prop.withId(prefix); + } + return null; + } + + +} diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/SpringPropertiesCompletionEngine.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/SpringPropertiesCompletionEngine.java new file mode 100644 index 000000000..9f48cd85e --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/completions/SpringPropertiesCompletionEngine.java @@ -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 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; + } + +} \ No newline at end of file diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/reconcile/SpringPropertiesReconcileEngine.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/reconcile/SpringPropertiesReconcileEngine.java index ea75dd9c1..bd76e8b84 100644 --- a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/reconcile/SpringPropertiesReconcileEngine.java +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/reconcile/SpringPropertiesReconcileEngine.java @@ -69,7 +69,7 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine { private final DelimitedListReconciler commaListReconciler = new DelimitedListReconciler(COMMA, this::reconcileType); private Parser parser = new AntlrParser(); - private boolean recordSyntaxErrors = false; + private boolean recordSyntaxErrors; public SpringPropertiesReconcileEngine(SpringPropertyIndexProvider provider, TypeUtilProvider typeUtilProvider) { this(provider, typeUtilProvider, true); @@ -103,8 +103,6 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine { } results.ast.getNodes(KeyValuePair.class).forEach(pair -> { -// Key fullName = pair.getKey(); -// String keyName = fullName.decode(); try { DocumentRegion propertyNameRegion = createRegion(doc, pair.getKey()); 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) int length = value.getLength(); try { - length = doc.get(value.getOffset(), value.getLength()).trim().length(); + length = doc.get(value.getOffset(), value.getLength()).length(); } catch (BadLocationException e) { // ignore } diff --git a/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesEditorTest.java b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesEditorTest.java index c8571ea06..c4a291a4f 100644 --- a/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesEditorTest.java +++ b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesEditorTest.java @@ -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.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.languageserver.testharness.Editor; @@ -47,6 +48,8 @@ import org.eclipse.lsp4j.Diagnostic; */ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { + private JavaProjectFinder javaProjectFinder; + @Test public void testReconcileCatchesParseError() throws Exception { Editor editor = newEditor("key\n"); editor.assertProblems("key|extraneous input"); @@ -66,28 +69,28 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { 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."); assertCompletion("ser<*>", "server.port=<*>"); assertCompletionDisplayString("ser<*>", "server.port : int"); } - @Ignore @Test public void testLoggingLevelCompletion() throws Exception { + @Test public void testLoggingLevelCompletion() throws Exception { data("logging.level", "java.util.Map", null, "Logging level per package."); assertCompletion("lolev<*>","logging.level.<*>"); } - @Ignore @Test public void testListCompletion() throws Exception { + @Test public void testListCompletion() throws Exception { data("foo.bars", "java.util.List", null, "List of bars in foo."); assertCompletion("foba<*>","foo.bars=<*>"); } - @Ignore @Test public void testInetAddresCompletion() throws Exception { + @Test public void testInetAddresCompletion() throws Exception { defaultTestData(); 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("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=<*>"); } - @Ignore @Test public void testEmptyPrefixProposalsSortedAlpabetically() throws Exception { + @Test public void testEmptyPrefixProposalsSortedAlpabetically() throws Exception { defaultTestData(); Editor editor = newEditor(""); List 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(); assertCompletionsVariations("liquibase.enabled=<*>", "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"); 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); 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"); useProject(p); @@ -481,7 +484,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { 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"); 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"); 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"); 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"); useProject(p); @@ -629,11 +632,11 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { assertCompletionsDisplayString("foo.data.", "wavelen : double", "name : String", - "next : demo.Color[RED, GREEN, BLUE]", + "next : demo.Color[BLUE, GREEN, RED]", "nested : demo.ColorData", "children : List", "mapped-children : Map", - "color-children : Map", + "color-children : Map", "tags : List", "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", null, "list of strings"); data("foo.ulist", "java.util.List", null, "list of strings"); data("foo.dlist", "java.util.List", null, "list of doubles"); @@ -684,7 +687,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { 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 //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 // 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"); useProject(p); @@ -840,7 +843,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { 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"); useProject(p); @@ -849,7 +852,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { 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"); 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"); 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"); 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. // 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")); 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")); 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")); assertCompletionWithLabel( "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", // "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") .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")); 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); 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); 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); 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") .valueHint("SOMETHING", "A value for something") .valueHint("ALTERNATE", "An alternative value"); @@ -1558,17 +1561,9 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { @Override protected SimpleLanguageServer newLanguageServer() { - return new ApplicationPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider); -// return new ApplicationPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider) { -// -// @Override -// protected IReconcileEngine getReconcileEngine() { -// SpringPropertiesReconcileEngine reconcileEngine = (SpringPropertiesReconcileEngine) super.getReconcileEngine(); -// reconcileEngine.setRecordSyntaxErrors(false); -// return reconcileEngine; -// } -// -// }; + ApplicationPropertiesLanguageServer server = new ApplicationPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder); + server.setMaxCompletionsNumber(-1); + return server; } /** diff --git a/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesLanguageServerTest.java b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesLanguageServerTest.java index c59cfa767..5f911a0f4 100644 --- a/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesLanguageServerTest.java +++ b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/application/properties/test/ApplicationPropertiesLanguageServerTest.java @@ -37,7 +37,7 @@ public class ApplicationPropertiesLanguageServerTest { } private LanguageServerHarness newHarness() throws Exception { - Callable f = () -> new ApplicationPropertiesLanguageServer((d) -> null, (d) -> null); + Callable f = () -> new ApplicationPropertiesLanguageServer((d) -> null, (d) -> null, (d) -> null); return new LanguageServerHarness(f); } diff --git a/vscode-extensions/vscode-application-yaml/package.json b/vscode-extensions/vscode-application-yaml/package.json index 6e2628671..0834ba4d1 100644 --- a/vscode-extensions/vscode-application-yaml/package.json +++ b/vscode-extensions/vscode-application-yaml/package.json @@ -49,8 +49,7 @@ }, "dependencies": { "portfinder": "^0.4.0", - "vscode-languageclient": "2.5.x", - "commons-vscode": "0.1.x" + "vscode-languageclient": "2.5.x" }, "devDependencies": { "typescript": "^1.8.5", diff --git a/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/ApplicationYamlLanguageServer.java b/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/ApplicationYamlLanguageServer.java index d2603823e..e2d398328 100644 --- a/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/ApplicationYamlLanguageServer.java +++ b/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/ApplicationYamlLanguageServer.java @@ -27,6 +27,7 @@ public class ApplicationYamlLanguageServer extends SimpleLanguageServer { private YamlASTProvider parser = new YamlParser(yaml); private SpringPropertyIndexProvider indexProvider; private TypeUtilProvider typeUtilProvider; + private VscodeCompletionEngineAdapter completionEngine; public ApplicationYamlLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, JavaProjectFinder javaProjectFinder) { this.indexProvider = indexProvider; @@ -57,10 +58,14 @@ public class ApplicationYamlLanguageServer extends SimpleLanguageServer { typeUtilProvider, RelaxedNameConfig.COMPLETION_DEFAULTS ); - VscodeCompletionEngine completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine); + completionEngine = new VscodeCompletionEngineAdapter(this, yamlCompletionEngine); documents.onCompletion(completionEngine::getCompletions); documents.onCompletionResolve(completionEngine::resolveCompletion); } + + public void setMaxCompletionsNumber(int number) { + completionEngine.setMaxCompletionsNumber(number); + } protected IReconcileEngine getReconcileEngine() { return new ApplicationYamlReconcileEngine(parser, indexProvider, typeUtilProvider); diff --git a/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/ApplicationYamlAssistContext.java b/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/ApplicationYamlAssistContext.java index 5bdbb3fb6..607f4dd54 100644 --- a/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/ApplicationYamlAssistContext.java +++ b/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/ApplicationYamlAssistContext.java @@ -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.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.completion.ProposalApplier; import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; diff --git a/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/application/yaml/ApplicationYamlEditorTest.java b/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/application/yaml/ApplicationYamlEditorTest.java index 28868452f..3fcc4048c 100644 --- a/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/application/yaml/ApplicationYamlEditorTest.java +++ b/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/application/yaml/ApplicationYamlEditorTest.java @@ -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")); assertCompletionWithLabel( "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); 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); 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", // "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")); //Check the metadata reflects the 'handle-as': @@ -3542,7 +3542,9 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { @Override protected SimpleLanguageServer newLanguageServer() { - return new ApplicationYamlLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder); + ApplicationYamlLanguageServer server = new ApplicationYamlLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder); + server.setMaxCompletionsNumber(-1); + return server; } }