Merge branch 'master' of github.com:spring-projects/sts4

This commit is contained in:
Kris De Volder
2016-11-14 11:10:22 -08:00
41 changed files with 1527 additions and 274 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
*.class
**/.classpath
**/classpath.txt
**/.idea
**/*.iml

View File

@@ -7,20 +7,66 @@
<content-type
base-type="org.eclipse.jdt.core.javaProperties"
default-charset="ISO-8859-1"
id="org.springframework.boot.ide.properties.applicationProperties"
id="org.springframework.boot.ide.properties.application.properties"
name="Spring Properties File"
priority="high">
</content-type>
<file-association
content-type="org.springframework.boot.ide.properties.applicationProperties"
content-type="org.springframework.boot.ide.properties.application.properties"
file-names="application.properties,application-dev.properties">
</file-association>
</extension>
<extension
point="org.eclipse.core.contenttype.contentTypes">
<content-type
base-type="org.eclipse.core.runtime.text"
default-charset="UTF-8"
id="org.springframework.boot.ide.properties.application.yml"
name="Spring Yaml Properties File"
priority="high">
</content-type>
<file-association
content-type="org.springframework.boot.ide.properties.application.yml"
file-names="application.yml,bootstrap.yml,application-dev.yml">
</file-association>
</extension>
<extension
point="org.eclipse.core.contenttype.contentTypes">
<content-type
base-type="org.eclipse.core.runtime.text"
default-charset="UTF-8"
id="org.springframework.boot.ide.manifest.yml"
name="Cloudfoundry Manifest"
priority="high">
</content-type>
<file-association
content-type="org.springframework.boot.ide.manifest.yml"
file-names="manifest.yml">
</file-association>
</extension>
<extension
point="org.eclipse.ui.editors">
<editorContentTypeBinding
contentTypeId="org.springframework.boot.ide.properties.applicationProperties"
contentTypeId="org.springframework.boot.ide.properties.application.properties"
editorId="org.eclipse.ui.genericeditor.GenericEditor">
</editorContentTypeBinding>
</extension>
<extension
point="org.eclipse.ui.editors">
<editorContentTypeBinding
contentTypeId="org.springframework.boot.ide.properties.application.yml"
editorId="org.eclipse.ui.genericeditor.GenericEditor">
</editorContentTypeBinding>
</extension>
<extension
point="org.eclipse.ui.editors">
<editorContentTypeBinding
contentTypeId="org.springframework.boot.ide.manifest.yml"
editorId="org.eclipse.ui.genericeditor.GenericEditor">
</editorContentTypeBinding>
</extension>

View File

@@ -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<Tuple2<String,String>, CacheEntry> cache = createCache();
//
// private class CacheEntry {
// boolean isComplete = false;
// int count = 0;
// Flux<StsValueHint> values;
//
// public CacheEntry(String query, Flux<StsValueHint> 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<StsValueHint> getValues(IJavaProject javaProject, String query) {
//// debug("CA query: "+query);
// Tuple2<String, String> 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.
// * <p>
// * Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache.
// */
// private Flux<StsValueHint> getValuesIncremental(IJavaProject javaProject, String query) {
//// debug("trying to solve "+query+" incrementally");
// 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<StsValueHint> getValuesAsycn(IJavaProject javaProject, String query);
//
// private Tuple2<String,String> key(IJavaProject javaProject, String query) {
// return Tuples.of(javaProject==null?null:javaProject.getElementName(), query);
// }
//
// protected <K,V> Cache<K,V> createCache() {
// return new LimitedTimeCache<>(Duration.ofMinutes(1));
// }
private Cache<Tuple2<String,String>, CacheEntry> cache = createCache();
private class CacheEntry {
boolean isComplete = false;
int count = 0;
Flux<StsValueHint> values;
public CacheEntry(String query, Flux<StsValueHint> 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<StsValueHint> getValues(IJavaProject javaProject, String query) {
Tuple2<String, String> 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.
* <p>
* Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache.
*/
private Flux<StsValueHint> getValuesIncremental(IJavaProject javaProject, String query) {
// debug("trying to solve "+query+" incrementally");
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<StsValueHint> getValuesAsycn(IJavaProject javaProject, String query);
private Tuple2<String,String> key(IJavaProject javaProject, String query) {
return Tuples.of(javaProject==null?null:javaProject.getElementName(), query);
}
protected <K,V> Cache<K,V> createCache() {
return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
}
public static void restoreDefaults() {
TIMEOUT = DEFAULT_TIMEOUT;

View File

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

View File

@@ -97,6 +97,11 @@ public class PropertyCompletionFactory {
return typeUtil.niceTypeName((Type) type);
}
@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());
}
}
}

View File

@@ -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());
}
/**

View File

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

View File

@@ -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

View File

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

View File

@@ -1,10 +1,7 @@
package org.springframework.ide.vscode.application.yaml.completions;
package org.springframework.ide.vscode.commons.languageserver.completion;
import java.util.concurrent.Callable;
import 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<DocumentEdits> createEdits) throws Exception {
return createEdits.call();
}

View File

@@ -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<CompletionList> 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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -182,80 +182,66 @@ public class SimpleTextDocumentService implements TextDocumentService {
@Override
public CompletableFuture<Hover> hover(TextDocumentPositionParams position) {
// TODO Auto-generated method stub
return null;
return Futures.of(null);
}
@Override
public CompletableFuture<SignatureHelp> signatureHelp(TextDocumentPositionParams position) {
// TODO Auto-generated method stub
return null;
return Futures.of(null);
}
@Override
public CompletableFuture<List<? extends Location>> definition(TextDocumentPositionParams position) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends SymbolInformation>> documentSymbol(DocumentSymbolParams params) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends Command>> codeAction(CodeActionParams params) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
@Override
public CompletableFuture<CodeLens> resolveCodeLens(CodeLens unresolved) {
// TODO Auto-generated method stub
return null;
return Futures.of(null);
}
@Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams params) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
@Override
public CompletableFuture<WorkspaceEdit> 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<Diagnostic> diagnostics) {
@@ -274,8 +260,7 @@ public class SimpleTextDocumentService implements TextDocumentService {
@Override
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(TextDocumentPositionParams position) {
// TODO Auto-generated method stub
return null;
return Futures.of(Collections.emptyList());
}
}

View File

@@ -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<String> getClasspathResources() {
return project.getBuild().getResources().stream().flatMap(resource -> {
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir(resource.getDirectory());
if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) {
scanner.setIncludes(resource.getIncludes().toArray(new String[resource.getIncludes().size()]));
}
if (resource.getExcludes() != null && !resource.getExcludes().isEmpty()) {
scanner.setExcludes(resource.getExcludes().toArray(new String[resource.getExcludes().size()]));
}
scanner.setCaseSensitive(false);
scanner.scan();
return Arrays.stream(scanner.getIncludedFiles());
});
}
}

View File

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

View File

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

View File

@@ -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<Node> 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<Node> getChildren() {
return children;
}
}
private static class Comment implements PropertiesAst.Comment {
private static class EmptyLine extends Node implements PropertiesAst.EmptyLine {
private EmptyLineContext context;
public EmptyLine(EmptyLineContext context) {
super();
this.context = context;
}
@Override
protected EmptyLineContext getContext() {
return context;
}
}
private static class Comment extends Node implements PropertiesAst.Comment {
private CommentLineContext context;
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();
}
}
}

View File

@@ -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>@\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>@\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 {

View File

@@ -31,7 +31,7 @@ public final class PropertiesAst {
* Retrieves all AST nodes
* @return List of AST nodes sorted by line number
*/
public List<? extends Node> getAllNodes() {
public List<Node> getAllNodes() {
return nodes;
}
@@ -48,6 +48,52 @@ public final class PropertiesAst {
return (List<T>) l;
}
/**
* Find node in AST corresponding to offset position
* @param offset Position in the text
* @return AST node corresponding to the offset position
*/
public Node findNode(int offset) {
return findNode(nodes, offset, 0, nodes.size() - 1);
}
private Node findNode(List<? extends Node> nodes, int offset, int start, int end) {
if (nodes == null) {
return null;
}
if (start == end) {
Node node = nodes.get(start);
if (node.getOffset() <= offset && offset <= node.getOffset() + node.getLength()) {
Node found = findChildNode(node, offset);
return found == null ? node : found;
} else {
return null;
}
} else if (start < end ) {
int pivotIndex = (start + end) / 2;
Node node = nodes.get(pivotIndex);
if (node.getOffset() > offset) {
return findNode(nodes, offset, start, pivotIndex - 1);
} else if (offset > node.getOffset() + node.getLength()) {
return findNode(nodes, offset, pivotIndex + 1, end);
} else {
Node found = findChildNode(node, offset);
return found == null ? node : found;
}
} else {
return null;
}
}
private Node findChildNode(Node node, int offset) {
if (node.getChildren() == null) {
return null;
} else {
return findNode(node.getChildren(), offset, 0, node.getChildren().size() - 1);
}
}
/**
* Java Properties AST node
*/
@@ -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<? extends Node> 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();
}
}

View File

@@ -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

View File

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

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="test-projects">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -1,5 +1,5 @@
/.classpath
/.project
**/.classpath
**/classpath.txt
**/.project
**/.settings/*.*

View File

@@ -37,4 +37,11 @@
<version>${junit-version}</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>test-projects</directory>
</resource>
</resources>
</build>
</project>

View File

@@ -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<String, IJavaProject> cache = CacheBuilder.newBuilder().build();
public Cache<String, IJavaProject> 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>() {
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<Path>() {
@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 {

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -49,7 +49,8 @@ export function activate(context: VSCode.ExtensionContext) {
// TODO: Remove <any> cast ones https://github.com/Microsoft/vscode-languageserver-node/issues/9 is resolved
documentSelector: [
<any> {language: 'ini', pattern: '**/application*.properties'},
<any> {language: 'java-properties', pattern: '**/application*.properties'}
<any> {language: 'java-properties', pattern: '**/application*.properties'},
<any> {language: 'properties', pattern: '**/application*.properties'}
],
synchronize: {
// Synchronize the setting section to the server:

View File

@@ -22,7 +22,8 @@
],
"activationEvents": [
"onLanguage:ini",
"onLanguage:java-properties"
"onLanguage:java-properties",
"onLanguage:properties"
],
"main": "./out/lib/Main",
"files": [

View File

@@ -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);
}

View File

@@ -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;
});
}

View File

@@ -0,0 +1,582 @@
package org.springframework.ide.vscode.application.properties.completions;
import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.completions.PropertyCompletionFactory;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProvider;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProviders;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.application.properties.metadata.hints.ValueHintHoverInfo;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.application.properties.reconcile.PropertyNavigator;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.completion.LazyProposalApplier;
import org.springframework.ide.vscode.commons.languageserver.util.BadLocationException;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.IDocument;
import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder;
import org.springframework.ide.vscode.commons.languageserver.util.TextDocument;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.EmptyLine;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Key;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value;
import com.google.common.collect.ImmutableList;
class PropertiesCompletionProposalsCalculator {
private static final Pattern SPACES = Pattern.compile(
"(\\s|\\\\\\s)*"
);
private static boolean isValuePrefixChar(char c) {
return !Character.isWhitespace(c) && c!=',';
}
private static final PrefixFinder valuePrefixFinder = new PrefixFinder() {
protected boolean isPrefixChar(char c) {
return isValuePrefixChar(c);
}
};
private static final PrefixFinder fuzzySearchPrefix = new PrefixFinder() {
protected boolean isPrefixChar(char c) {
return !Character.isWhitespace(c);
}
};
private static final PrefixFinder navigationPrefixFinder = new PrefixFinder() {
public String getPrefix(IDocument doc, int offset) {
String prefix = super.getPrefix(doc, offset);
//Check if character before looks like 'navigation'.. otherwise don't
// return a navigationPrefix.
char charBefore = getCharBefore(doc, prefix, offset);
if (charBefore=='.' || charBefore==']') {
return prefix;
}
return null;
}
private char getCharBefore(IDocument doc, String prefix, int offset) {
try {
if (prefix!=null) {
int offsetBefore = offset-prefix.length()-1;
if (offsetBefore>=0) {
return doc.getChar(offsetBefore);
}
}
} catch (BadLocationException e) {
//ignore
}
return 0;
}
protected boolean isPrefixChar(char c) {
return !Character.isWhitespace(c) && c!=']' && c!=']' && c!='.';
}
};
private FuzzyMap<PropertyInfo> index;
private TypeUtil typeUtil;
private PropertyCompletionFactory completionFactory;
private IDocument doc;
private int offset;
private boolean preferLowerCaseEnums;
private AntlrParser parser;
PropertiesCompletionProposalsCalculator(FuzzyMap<PropertyInfo> index, TypeUtil typeUtil, PropertyCompletionFactory completionFactory, IDocument doc, int offset, boolean preferLowerCaseEnums) {
this.index = index;
this.typeUtil = typeUtil;
this.completionFactory = completionFactory;
this.doc = doc;
this.offset = offset;
this.preferLowerCaseEnums = preferLowerCaseEnums;
this.parser = new AntlrParser();
}
/**
* Create completions proposals in the context of a properties text editor.
*/
public Collection<ICompletionProposal> calculate() throws BadLocationException {
ParseResults parseResults = parser.parse(doc.get());
Node node = parseResults.ast.findNode(offset);
if (node instanceof Value) {
return getValueCompletions((Value)node);
} else if (node instanceof Key || node instanceof EmptyLine || node == null) {
return getPropertyCompletions();
}
return Collections.emptyList();
}
private Collection<ICompletionProposal> getNavigationProposals() {
String navPrefix = navigationPrefixFinder.getPrefix(doc, offset);
try {
if (navPrefix!=null) {
int navOffset = offset-navPrefix.length()-1; //offset of 'nav' operator char (i.e. '.' or ']').
navPrefix = fuzzySearchPrefix.getPrefix(doc, navOffset);
if (navPrefix!=null && !navPrefix.isEmpty()) {
PropertyInfo prop = findLongestValidProperty(index, navPrefix);
if (prop!=null) {
int regionStart = navOffset-navPrefix.length();
Collection<ICompletionProposal> hintProposals = getKeyHintProposals(prop, navOffset);
if (CollectionUtil.hasElements(hintProposals)) {
return hintProposals;
}
PropertyNavigator navigator = new PropertyNavigator(doc, null, typeUtil, new DocumentRegion(doc, regionStart, navOffset));
Type type = navigator.navigate(regionStart+prop.getId().length(), TypeParser.parse(prop.getType()));
if (type!=null) {
return getNavigationProposals(type, navOffset);
}
}
}
}
} catch (Exception e) {
Log.log(e);
}
return Collections.emptyList();
}
private Collection<ICompletionProposal> getKeyHintProposals(PropertyInfo prop, int navOffset) {
HintProvider hintProvider = prop.getHints(typeUtil, false);
if (!HintProviders.isNull(hintProvider)) {
String query = textBetween(doc, navOffset+1, offset);
List<TypedProperty> hintProperties = hintProvider.getPropertyHints(query);
if (CollectionUtil.hasElements(hintProperties)) {
return createPropertyProposals(TypeParser.parse(prop.getType()), navOffset, query, hintProperties);
}
}
return ImmutableList.of();
}
private String textBetween(IDocument doc, int start, int end) {
if (end > doc.getLength()) {
end = doc.getLength();
}
if (start>doc.getLength()) {
start = doc.getLength();
}
if (start<0) {
start = 0;
}
if (end < 0) {
end = 0;
}
if (start<end) {
try {
return doc.get(start, end-start);
} catch (BadLocationException e) {
}
}
return "";
}
/**
* @param type Type of the expression leading upto the 'nav' operator
* @param navOffset Offset of the nav operator (either ']' or '.'
* @param offset Offset of the cursor where CA was requested.
*/
private Collection<ICompletionProposal> getNavigationProposals(Type type, int navOffset) {
try {
char navOp = doc.getChar(navOffset);
if (navOp=='.') {
String prefix = doc.get(navOffset+1, offset-(navOffset+1));
EnumCaseMode caseMode = caseMode(prefix);
List<TypedProperty> objectProperties = typeUtil.getProperties(type, caseMode, BeanPropertyNameMode.HYPHENATED);
//Note: properties editor itself deals with relaxed names. So it expects the properties here to be returned in hyphenated form only.
if (objectProperties!=null && !objectProperties.isEmpty()) {
return createPropertyProposals(type, navOffset, prefix, objectProperties);
}
} else {
//TODO: other cases ']' or '[' ?
}
} catch (Exception e) {
Log.log(e);
}
return Collections.emptyList();
}
protected Collection<ICompletionProposal> createPropertyProposals(Type type, int navOffset,
String prefix, List<TypedProperty> objectProperties) {
ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
for (TypedProperty prop : objectProperties) {
double score = FuzzyMatcher.matchScore(prefix, prop.getName());
if (score!=0) {
Type valueType = prop.getType();
String postFix = propertyCompletionPostfix(typeUtil, valueType);
DocumentEdits edits = new DocumentEdits(doc);
edits.delete(navOffset+1, offset);
edits.insert(offset, prop.getName()+postFix);
proposals.add(
completionFactory.beanProperty(doc, null, type, prefix, prop, score, edits, typeUtil)
);
}
}
return proposals;
}
/**
* Determines the EnumCaseMode used to generate completion candidates based on prefix.
*/
protected EnumCaseMode caseMode(String prefix) {
EnumCaseMode caseMode;
if ("".equals(prefix)) {
caseMode = preferLowerCaseEnums?EnumCaseMode.LOWER_CASE:EnumCaseMode.ORIGNAL;
} else {
caseMode = Character.isLowerCase(prefix.charAt(0))?EnumCaseMode.LOWER_CASE:EnumCaseMode.ORIGNAL;
}
return caseMode;
}
protected static String propertyCompletionPostfix(TypeUtil typeUtil, Type type) {
String postfix = "";
if (type!=null) {
if (typeUtil.isAssignableType(type)) {
postfix = "=";
} else if (TypeUtil.isBracketable(type)) {
postfix = "[";
} else if (typeUtil.isDotable(type)) {
postfix = ".";
}
}
return postfix;
}
// public static boolean isAssign(char assign) {
// return assign==':'||assign=='=';
// }
//
// private KeyValuePair getAstNodeLine(IDocument doc, int offset) {
// List<KeyValuePair> pairs = parser.parse(doc.get()).ast.getNodes(KeyValuePair.class);
// return findPair(pairs, offset, 0, pairs.size() - 1);
// }
//
// private KeyValuePair findPair(List<KeyValuePair> pairs, int offset, int start, int end) {
// if (start == end) {
// KeyValuePair pair = pairs.get(start);
// if (pair.getOffset() <= offset && offset <= pair.getOffset() + pair.getLength()) {
// return pair;
// } else {
// return null;
// }
// } else if (start < end ) {
// int pivotIndex = (start + end) / 2;
// KeyValuePair pair = pairs.get(pivotIndex);
// if (pair.getOffset() > offset) {
// return findPair(pairs, offset, start, pivotIndex - 1);
// } else if (offset > pair.getOffset() + pair.getLength()) {
// return findPair(pairs, offset, pivotIndex + 1, end);
// } else {
// return pair;
// }
// } else {
// return null;
// }
// }
// private HoverInfo getValueHoverInfo(DocumentRegion value) {
// try {
// String valueString = value.toString();
// IDocument doc = value.getDocument();
// ITypedRegion valuePartition = getPartition(value.getDocument(), value.getStart());
// int valuePartitionStart = valuePartition.getOffset();
// String propertyName = fuzzySearchPrefix.getPrefix(doc, valuePartitionStart); //note: no need to skip whitespace backwards.
// //because value partition includes whitespace around the assignment
//
// Type type = getValueType(propertyName);
// if (TypeUtil.isArray(type) || TypeUtil.isList(type)) {
// //It is useful to provide content assist for the values in the list when entering a list
// type = TypeUtil.getDomainType(type);
// }
// if (TypeUtil.isClass(type)) {
// //Special case. We want to provide hoverinfos more liberally than what's suggested for completions (i.e. even class names
// //that are not suggested by the hints because they do not meet subtyping constraints should be hoverable and linkable!
// StsValueHint hint = StsValueHint.className(valueString, typeUtil);
// if (hint!=null) {
// return new ValueHintHoverInfo(hint);
// }
// }
// //Hack: pretend to invoke content-assist at the end of the value text. This should provide hints applicable to that value
// // then show hoverinfo based on that. That way we can avoid duplication a lot of similar logic to compute hoverinfos and hyperlinks.
// Collection<StsValueHint> hints = getValueHints(valueString, propertyName, EnumCaseMode.ALIASED);
// if (hints!=null) {
// for (StsValueHint h : hints) {
// if (valueString.equals(h.getValue())) {
// return new ValueHintHoverInfo(h);
// }
// }
// }
// } catch (BadLocationException e) {
// Log.log(e);
// }
// return null;
// }
private Collection<ICompletionProposal> getValueCompletions(Value value) {
DocumentRegion valueRegion = createRegion(doc, value).trimStart(SPACES).trimEnd(SPACES);
String query = valuePrefixFinder.getPrefix(doc, offset, valueRegion.getStart());
int startOfValue = offset - query.length();
EnumCaseMode caseMode = caseMode(query);
// note: no need to skip whitespace backwards.
String propertyName = /*fuzzySearchPrefix.getPrefix(doc, pair.getOffset())*/value.getParent().getKey().decode();
// because value partition includes whitespace around the assignment
if (propertyName != null) {
Collection<StsValueHint> valueCompletions = getValueHints(query, propertyName, caseMode);
if (valueCompletions != null && !valueCompletions.isEmpty()) {
ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
for (StsValueHint hint : valueCompletions) {
String valueCandidate = hint.getValue();
double score = FuzzyMatcher.matchScore(query, valueCandidate);
if (score != 0) {
DocumentEdits edits = new DocumentEdits(doc);
edits.delete(startOfValue, offset);
edits.insert(offset, valueCandidate);
proposals.add(completionFactory.valueProposal(valueCandidate, query, getValueType(propertyName),
score, edits, new ValueHintHoverInfo(hint))
// new ValueProposal(startOfValue, valuePrefix,
// valueCandidate, i)
);
}
}
return proposals;
}
}
return Collections.emptyList();
}
private DocumentRegion createRegion(IDocument doc, Node value) {
// Trim trailing spaces (there is no leading white space already)
int length = value.getLength();
try {
length = doc.get(value.getOffset(), value.getLength()).trim().length();
} catch (BadLocationException e) {
// ignore
}
return new DocumentRegion(doc, value.getOffset(), value.getOffset() + length);
}
private Collection<StsValueHint> getValueHints(String query, String propertyName, EnumCaseMode caseMode) {
Type type = getValueType(propertyName);
if (TypeUtil.isArray(type) || TypeUtil.isList(type)) {
//It is useful to provide content assist for the values in the list when entering a list
type = TypeUtil.getDomainType(type);
}
List<StsValueHint> allHints = new ArrayList<>();
{
Collection<StsValueHint> hints = typeUtil.getHintValues(type, query, caseMode);
if (CollectionUtil.hasElements(hints)) {
allHints.addAll(hints);
}
}
{
PropertyInfo prop = index.findLongestCommonPrefixEntry(propertyName);
if (prop!=null) {
HintProvider hintProvider = prop.getHints(typeUtil, false);
if (!HintProviders.isNull(hintProvider)) {
allHints.addAll(hintProvider.getValueHints(query));
}
}
}
return allHints;
}
/**
* Determine the value type for a give propertyName.
*/
protected Type getValueType(String propertyName) {
try {
PropertyInfo prop = index.get(propertyName);
if (prop!=null) {
return TypeParser.parse(prop.getType());
} else {
prop = findLongestValidProperty(index, propertyName);
if (prop!=null) {
TextDocument doc = new TextDocument(null);
doc.setText(propertyName);
PropertyNavigator navigator = new PropertyNavigator(doc, null, typeUtil, new DocumentRegion(doc, 0, doc.getLength()));
return navigator.navigate(prop.getId().length(), TypeParser.parse(prop.getType()));
}
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
private List<Match<PropertyInfo>> findMatches(String prefix) {
List<Match<PropertyInfo>> matches = index.find(camelCaseToHyphens(prefix));
return matches;
}
private Collection<ICompletionProposal> getPropertyCompletions() throws BadLocationException {
Collection<ICompletionProposal> navProposals = getNavigationProposals();
if (!navProposals.isEmpty()) {
return navProposals;
}
return getFuzzyCompletions();
}
protected Collection<ICompletionProposal> getFuzzyCompletions() {
final String prefix = fuzzySearchPrefix.getPrefix(doc, offset);
if (prefix != null) {
Collection<Match<PropertyInfo>> matches = findMatches(prefix);
if (matches!=null && !matches.isEmpty()) {
ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(matches.size());
for (final Match<PropertyInfo> match : matches) {
DocumentEdits docEdits;
try {
docEdits = LazyProposalApplier.from(() -> {
Type type = TypeParser.parse(match.data.getType());
DocumentEdits edits = new DocumentEdits(doc);
edits.delete(offset-prefix.length(), offset);
edits.insert(offset, match.data.getId() + propertyCompletionPostfix(typeUtil, type));
return edits;
});
proposals.add(completionFactory.property(doc, docEdits, match, typeUtil));
} catch (Exception e) {
Log.log(e);
}
}
return proposals;
}
}
return Collections.emptyList();
}
// public HoverInfo getHoverInfo(IDocument doc, IRegion _region) {
// debug("getHoverInfo("+_region+")");
//
// //The delegate 'getHoverRegion' for spring propery editor will return smaller word regions.
// // we must ensure to use our own region finder to identify correct property name.
// ITypedRegion region = getHoverRegion(doc, _region.getOffset());
// if (region!=null) {
// String contentType = region.getType();
// try {
// if (contentType.equals(IDocument.DEFAULT_CONTENT_TYPE)) {
// debug("hoverRegion = "+region);
// PropertyInfo best = findBestHoverMatch(doc.get(region.getOffset(), region.getLength()).trim());
// if (best!=null) {
// return new SpringPropertyHoverInfo(documentContextFinder.getJavaProject(doc), best);
// }
// } else if (contentType.equals(IPropertiesFilePartitions.PROPERTY_VALUE)) {
// return getValueHoverInfo(new DocumentRegion(doc, region));
// }
// } catch (Exception e) {
// SpringPropertiesEditorPlugin.log(e);
// }
// }
// return null;
// }
//
// public ITypedRegion getHoverRegion(IDocument document, int offset) {
// try {
// ITypedRegion candidate = getPartition(document, offset);
// if (candidate!=null) {
// String type = candidate.getType();
// if (IDocument.DEFAULT_CONTENT_TYPE.equals(type)) {
// return candidate;
// } else if (IPropertiesFilePartitions.PROPERTY_VALUE.equals(type)) {
// DocumentRegion valueRegion = new DocumentRegion(document, candidate).trimStart(ASSIGN);
// return getValueHoverRegion(valueRegion, valueRegion.toRelative(offset));
// }
// }
// } catch (Exception e) {
// SpringPropertiesEditorPlugin.log(e);
// }
// return null;
// }
//
// private ITypedRegion getValueHoverRegion(DocumentRegion r, int offset) {
// int len = r.length();
// if (offset>=0 && offset<=len) {
// int start = offset;
// while (start>0 && isValuePrefixChar(r.charAt(start-1))) {
// start--;
// }
// int end = offset;
// while (end<len && isValuePrefixChar(r.charAt(end))) {
// end++;
// }
// r = r.subSequence(start, end);
// if (!r.isEmpty()) {
// return r.asTypedRegion(IPropertiesFilePartitions.PROPERTY_VALUE);
// }
// }
// return null;
// }
// /**
// * Search known properties for the best 'match' to show as hover data.
// */
// private PropertyInfo findBestHoverMatch(String propName) {
// //TODO: optimize, should be able to use index's treemap to find this without iterating all entries.
// PropertyInfo best = null;
// int bestCommonPrefixLen = 0; //We try to pick property with longest common prefix
// int bestExtraLen = Integer.MAX_VALUE;
// for (PropertyInfo candidate : index) {
// int commonPrefixLen = StringUtil.commonPrefixLength(propName, candidate.getId());
// int extraLen = candidate.getId().length()-commonPrefixLen;
// if (commonPrefixLen==propName.length() && extraLen==0) {
// //exact match found, can stop searching for better matches
// return candidate;
// }
// //candidate is better if...
// if (commonPrefixLen>bestCommonPrefixLen // it has a longer common prefix
// || commonPrefixLen==bestCommonPrefixLen && extraLen<bestExtraLen //or same common prefix but fewer extra chars
// ) {
// bestCommonPrefixLen = commonPrefixLen;
// bestExtraLen = extraLen;
// best = candidate;
// }
// }
// return best;
// }
/**
* Find the longest known property that is a prefix of the given name. Here prefix does not mean
* 'string prefix' but a prefix in the sense of treating '.' as a kind of separators. So
* 'prefix' is not allowed to end in the middle of a 'segment'.
*/
public static PropertyInfo findLongestValidProperty(FuzzyMap<PropertyInfo> index, String name) {
int bracketPos = name.indexOf('[');
int endPos = bracketPos>=0?bracketPos:name.length();
PropertyInfo prop = null;
String prefix = null;
while (endPos>0 && prop==null) {
prefix = name.substring(0, endPos);
String canonicalPrefix = camelCaseToHyphens(prefix);
prop = index.get(canonicalPrefix);
if (prop==null) {
endPos = name.lastIndexOf('.', endPos-1);
}
}
if (prop!=null) {
//We should meet caller's expectation that matched properties returned by this method
// match the names exactly even if we found them using relaxed name matching.
return prop.withId(prefix);
}
return null;
}
}

View File

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

View File

@@ -69,7 +69,7 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine {
private final DelimitedListReconciler commaListReconciler = new DelimitedListReconciler(COMMA, this::reconcileType);
private 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
}

View File

@@ -27,6 +27,7 @@ import org.springframework.ide.vscode.application.properties.metadata.CachingVal
import org.springframework.ide.vscode.application.properties.metadata.PropertiesLoader;
import org.springframework.ide.vscode.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<java.lang.String,java.lang.Object>", 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<java.lang.String>", 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<CompletionItem> completions = editor.getCompletions();
@@ -110,7 +113,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
}
}
@Ignore @Test public void testValueCompletion() throws Exception {
@Test public void testValueCompletion() throws Exception {
defaultTestData();
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<demo.ColorData>",
"mapped-children : Map<String, demo.ColorData>",
"color-children : Map<demo.Color[RED, GREEN, BLUE], demo.ColorData>",
"color-children : Map<demo.Color[BLUE, GREEN, RED], demo.ColorData>",
"tags : List<String>",
"funky : boolean"
);
@@ -675,7 +678,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
);
}
@Ignore @Test public void testListOfAtomicCompletions() throws Exception {
@Test public void testListOfAtomicCompletions() throws Exception {
data("foo.slist", "java.util.List<java.lang.String>", null, "list of strings");
data("foo.ulist", "java.util.List<Unknown>", null, "list of strings");
data("foo.dlist", "java.util.List<java.lang.Double>", null, "list of doubles");
@@ -684,7 +687,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
assertCompletionsVariations("foo.sl<*>", "foo.slist=<*>");
}
@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;
}
/**

View File

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

View File

@@ -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",

View File

@@ -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);

View File

@@ -35,6 +35,7 @@ import org.springframework.ide.vscode.application.properties.metadata.util.Fuzzy
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.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;

View File

@@ -2987,7 +2987,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
);
}
@Ignore @Test public void testPropertyMapKeyCompletions() throws Exception {
@Test public void testPropertyMapKeyCompletions() throws Exception {
useProject(createPredefinedMavenProject("empty-boot-1.3.0-app"));
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;
}
}