diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java
index 82295b53a..f47bd2042 100644
--- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java
+++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java
@@ -75,7 +75,7 @@ public class BootJavaCompletionEngine implements ICompletionEngine {
System.out.println("AST node found: " + node.getClass().getName());
Collection completions = new ArrayList();
completions.addAll(collectCompletionsForAnnotations(node, offset, document));
- completions.addAll(snippets.getCompletions(document, offset, node));
+ completions.addAll(snippets.getCompletions(document, offset, node, cu));
return completions;
}
diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/imports/ImportRewrite.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/imports/ImportRewrite.java
new file mode 100644
index 000000000..7cb23c656
--- /dev/null
+++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/imports/ImportRewrite.java
@@ -0,0 +1,558 @@
+/*******************************************************************************
+ * Derived from:
+ * org.eclipse.jdt.core.dom.rewrite.ImportRewrite
+ *
+ * for use in STS4, where IProject and ICompilationUnit are not available when parsing a Java source.
+ *
+ * Original license:
+ *
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * John Glassmyer - import group sorting is broken - https://bugs.eclipse.org/430303
+ * Lars Vogel - Contributions for
+ * Bug 473178
+ *******************************************************************************/
+package org.springframework.ide.vscode.boot.java.jdt.imports;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
+import org.eclipse.jdt.core.dom.Comment;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.ImportDeclaration;
+import org.eclipse.jdt.core.dom.PackageDeclaration;
+import org.eclipse.jdt.core.dom.PrimitiveType;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
+import org.springframework.ide.vscode.commons.util.text.IDocument;
+
+
+/**
+ * The {@link ImportRewrite} helps updating imports following a import order and on-demand imports threshold as configured by a project.
+ *
+ * The import rewrite is created on a compilation unit and collects references to types that are added or removed. When adding imports, e.g. using
+ * {@link #addImport(String)}, the import rewrite evaluates if the type can be imported and returns the a reference to the type that can be used in code.
+ * This reference is either unqualified if the import could be added, or fully qualified if the import failed due to a conflict with another element of the same name.
+ *
+ *
+ * On {@link #rewriteImports(IProgressMonitor)} the rewrite translates these descriptions into
+ * text edits that can then be applied to the original source. The rewrite infrastructure tries to generate minimal text changes and only
+ * works on the import statements. It is possible to combine the result of an import rewrite with the result of a {@link org.eclipse.jdt.core.dom.rewrite.ASTRewrite}
+ * as long as no import statements are modified by the AST rewrite.
+ *
+ * The options controlling the import order and on-demand thresholds are:
+ *
- {@link #setImportOrder(String[])} specifies the import groups and their preferred order
+ * - {@link #setOnDemandImportThreshold(int)} specifies the number of imports in a group needed for a on-demand import statement (star import)
+ * - {@link #setStaticOnDemandImportThreshold(int)} specifies the number of static imports in a group needed for a on-demand import statement (star import)
+ *
+ * This class is not intended to be subclassed.
+ *
+ * @since 3.2
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public final class ImportRewrite {
+
+ /**
+ * A {@link ImportRewrite.ImportRewriteContext} can optionally be used in e.g. {@link ImportRewrite#addImport(String, ImportRewrite.ImportRewriteContext)} to
+ * give more information about the types visible in the scope. These types can be for example inherited inner types where it is
+ * unnecessary to add import statements for.
+ *
+ *
+ *
+ * This class can be implemented by clients.
+ *
+ */
+ public static abstract class ImportRewriteContext {
+
+ /**
+ * Result constant signaling that the given element is know in the context.
+ */
+ public final static int RES_NAME_FOUND= 1;
+
+ /**
+ * Result constant signaling that the given element is not know in the context.
+ */
+ public final static int RES_NAME_UNKNOWN= 2;
+
+ /**
+ * Result constant signaling that the given element is conflicting with an other element in the context.
+ */
+ public final static int RES_NAME_CONFLICT= 3;
+
+ /**
+ * Result constant signaling that the given element must be imported explicitly (and must not be folded into
+ * an on-demand import or filtered as an implicit import).
+ *
+ * @since 3.11
+ */
+ public final static int RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT= 4;
+
+ /**
+ * Kind constant specifying that the element is a type import.
+ */
+ public final static int KIND_TYPE= 1;
+
+ /**
+ * Kind constant specifying that the element is a static field import.
+ */
+ public final static int KIND_STATIC_FIELD= 2;
+
+ /**
+ * Kind constant specifying that the element is a static method import.
+ */
+ public final static int KIND_STATIC_METHOD= 3;
+
+ /**
+ * Searches for the given element in the context and reports if the element is known ({@link #RES_NAME_FOUND}),
+ * unknown ({@link #RES_NAME_UNKNOWN}), unknown in the context but known to require an explicit import
+ * ({@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}), or if its name conflicts ({@link #RES_NAME_CONFLICT})
+ * with an other element.
+ *
+ * @param qualifier The qualifier of the element, can be package or the qualified name of a type
+ * @param name The simple name of the element; either a type, method or field name or * for on-demand imports.
+ * @param kind The kind of the element. Can be either {@link #KIND_TYPE}, {@link #KIND_STATIC_FIELD} or
+ * {@link #KIND_STATIC_METHOD}. Implementors should be prepared for new, currently unspecified kinds and return
+ * {@link #RES_NAME_UNKNOWN} by default.
+ * @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN},
+ * {@link #RES_NAME_CONFLICT}, or {@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}.
+ */
+ public abstract int findInContext(String qualifier, String name, int kind);
+ }
+
+ private static final char STATIC_PREFIX= 's';
+ private static final char NORMAL_PREFIX= 'n';
+
+ private final ImportRewriteContext defaultContext;
+
+ private final CompilationUnit astRoot;
+
+ private final boolean restoreExistingImports;
+ private final List existingImports;
+
+
+ private List addedImports;
+
+ /**
+ * Simple names of non-static imports which must not be reduced into on-demand imports
+ * or filtered out as implicit.
+ */
+ private Set typeExplicitSimpleNames;
+
+
+ private boolean filterImplicitImports;
+ private boolean useContextToFilterImplicitImports;
+
+
+ /**
+ * Creates an {@link ImportRewrite} from an AST ({@link CompilationUnit}). The AST has to be created from an
+ * {@link ICompilationUnit}, that means {@link ASTParser#setSource(ICompilationUnit)} has been used when creating the
+ * AST. If restoreExistingImports is true, all existing imports are kept, and new imports
+ * will be inserted at best matching locations. If restoreExistingImports is false, the
+ * existing imports will be removed and only the newly added imports will be created.
+ *
+ * Note that this method is more efficient than using {@link #create(ICompilationUnit, boolean)} if an AST is already available.
+ *
+ * @param astRoot the AST root node to create the imports for
+ * @param restoreExistingImports specifies if the existing imports should be kept or removed.
+ * @return the created import rewriter.
+ * @throws IllegalArgumentException thrown when the passed AST is null or was not created from a compilation unit.
+ */
+ public static ImportRewrite create(CompilationUnit astRoot, boolean restoreExistingImports) {
+ if (astRoot == null) {
+ throw new IllegalArgumentException("AST must not be null"); //$NON-NLS-1$
+ }
+
+ List existingImport= null;
+ if (restoreExistingImports) {
+ existingImport= new ArrayList();
+ List imports= astRoot.imports();
+ for (int i= 0; i < imports.size(); i++) {
+ ImportDeclaration curr= (ImportDeclaration) imports.get(i);
+ StringBuffer buf= new StringBuffer();
+ buf.append(curr.isStatic() ? STATIC_PREFIX : NORMAL_PREFIX).append(curr.getName().getFullyQualifiedName());
+ if (curr.isOnDemand()) {
+ if (buf.length() > 1)
+ buf.append('.');
+ buf.append('*');
+ }
+ existingImport.add(buf.toString());
+ }
+ }
+ return new ImportRewrite(astRoot, existingImport);
+ }
+
+ private ImportRewrite(CompilationUnit astRoot, List existingImports) {
+ this.astRoot= astRoot; // might be null
+ if (existingImports != null) {
+ this.existingImports= existingImports;
+ this.restoreExistingImports= !existingImports.isEmpty();
+ } else {
+ this.existingImports= new ArrayList();
+ this.restoreExistingImports= false;
+ }
+ this.filterImplicitImports= true;
+ // consider that no contexts are used
+ this.useContextToFilterImplicitImports = false;
+
+ this.defaultContext= new ImportRewriteContext() {
+ @Override
+ public int findInContext(String qualifier, String name, int kind) {
+ return findInImports(qualifier, name, kind);
+ }
+ };
+ this.addedImports= new ArrayList<>();
+ this.typeExplicitSimpleNames = new HashSet<>();
+ }
+
+ /**
+ * Returns the default rewrite context that only knows about the imported types. Clients
+ * can write their own context and use the default context for the default behavior.
+ * @return the default import rewrite context.
+ */
+ public ImportRewriteContext getDefaultImportRewriteContext() {
+ return this.defaultContext;
+ }
+
+ /**
+ * Specifies that implicit imports (for types in java.lang, types in the same package as the rewrite
+ * compilation unit, and types in the compilation unit's main type) should not be created, except if necessary to
+ * resolve an on-demand import conflict.
+ *
+ * The filter is enabled by default.
+ *
+ *
+ * Note: {@link #setUseContextToFilterImplicitImports(boolean)} can be used to filter implicit imports
+ * when a context is used.
+ *
+ *
+ * @param filterImplicitImports
+ * if true, implicit imports will be filtered
+ *
+ * @see #setUseContextToFilterImplicitImports(boolean)
+ */
+ public void setFilterImplicitImports(boolean filterImplicitImports) {
+ this.filterImplicitImports= filterImplicitImports;
+ }
+
+ /**
+ * Sets whether a context should be used to properly filter implicit imports.
+ *
+ * By default, the option is disabled to preserve pre-3.6 behavior.
+ *
+ *
+ * When this option is set, the context passed to the addImport*(...) methods is used to determine
+ * whether an import can be filtered because the type is implicitly visible. Note that too many imports
+ * may be kept if this option is set and addImport*(...) methods are called without a context.
+ *
+ *
+ * @param useContextToFilterImplicitImports the given setting
+ *
+ * @see #setFilterImplicitImports(boolean)
+ * @since 3.6
+ */
+ public void setUseContextToFilterImplicitImports(boolean useContextToFilterImplicitImports) {
+ this.useContextToFilterImplicitImports = useContextToFilterImplicitImports;
+ }
+
+ private static int compareImport(char prefix, String qualifier, String name, String curr) {
+ if (curr.charAt(0) != prefix || !curr.endsWith(name)) {
+ return ImportRewriteContext.RES_NAME_UNKNOWN;
+ }
+
+ curr= curr.substring(1); // remove the prefix
+
+ if (curr.length() == name.length()) {
+ if (qualifier.length() == 0) {
+ return ImportRewriteContext.RES_NAME_FOUND;
+ }
+ return ImportRewriteContext.RES_NAME_CONFLICT;
+ }
+ // at this place: curr.length > name.length
+
+ int dotPos= curr.length() - name.length() - 1;
+ if (curr.charAt(dotPos) != '.') {
+ return ImportRewriteContext.RES_NAME_UNKNOWN;
+ }
+ if (qualifier.length() != dotPos || !curr.startsWith(qualifier)) {
+ return ImportRewriteContext.RES_NAME_CONFLICT;
+ }
+ return ImportRewriteContext.RES_NAME_FOUND;
+ }
+
+ /**
+ * Not API, package visibility as accessed from an anonymous type
+ */
+ /* package */ final int findInImports(String qualifier, String name, int kind) {
+ boolean allowAmbiguity= (kind == ImportRewriteContext.KIND_STATIC_METHOD) || (name.length() == 1 && name.charAt(0) == '*');
+ List imports= this.existingImports;
+ char prefix= (kind == ImportRewriteContext.KIND_TYPE) ? NORMAL_PREFIX : STATIC_PREFIX;
+
+ for (int i= imports.size() - 1; i >= 0 ; i--) {
+ String curr= (String) imports.get(i);
+ int res= compareImport(prefix, qualifier, name, curr);
+ if (res != ImportRewriteContext.RES_NAME_UNKNOWN) {
+ if (!allowAmbiguity || res == ImportRewriteContext.RES_NAME_FOUND) {
+ if (prefix != STATIC_PREFIX) {
+ return res;
+ }
+ }
+ }
+ }
+
+ String packageName = getPackageName();
+ if (kind == ImportRewriteContext.KIND_TYPE) {
+ if (this.filterImplicitImports && this.useContextToFilterImplicitImports) {
+
+ // [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
+
+// String mainTypeSimpleName= JavaCore.removeJavaLikeExtension(this.compilationUnit.getElementName());
+// String mainTypeName= Util.concatenateName(packageName, mainTypeSimpleName, '.');
+// if (qualifier.equals(packageName)
+// || mainTypeName.equals(Util.concatenateName(qualifier, name, '.'))) {
+// return ImportRewriteContext.RES_NAME_FOUND;
+// }
+
+ if (this.astRoot != null) {
+ List types = this.astRoot.types();
+ int nTypes = types.size();
+ for (int i = 0; i < nTypes; i++) {
+ AbstractTypeDeclaration type = types.get(i);
+ SimpleName simpleName = type.getName();
+ if (simpleName.getIdentifier().equals(name)) {
+ return qualifier.equals(packageName)
+ ? ImportRewriteContext.RES_NAME_FOUND
+ : ImportRewriteContext.RES_NAME_CONFLICT;
+ }
+ }
+ } else {
+
+ // [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
+// try {
+// IType[] types = this.compilationUnit.getTypes();
+// int nTypes = types.length;
+// for (int i = 0; i < nTypes; i++) {
+// IType type = types[i];
+// String typeName = type.getElementName();
+// if (typeName.equals(name)) {
+// return qualifier.equals(packageName)
+// ? ImportRewriteContext.RES_NAME_FOUND
+// : ImportRewriteContext.RES_NAME_CONFLICT;
+// }
+// }
+// } catch (JavaModelException e) {
+// // don't want to throw an exception here
+// }
+ }
+ }
+ }
+
+ return ImportRewriteContext.RES_NAME_UNKNOWN;
+ }
+
+ private String getPackageName() {
+ // [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
+// this.compilationUnit.getParent().getElementName();
+ return this.astRoot.getPackage().getName().getFullyQualifiedName();
+ }
+
+ /**
+ * Adds a new import to the rewriter's record and returns a type reference that can be used
+ * in the code. The type binding can only be an array or non-generic type.
+ *
+ * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
+ *
+ *
+ * The content of the compilation unit itself is actually not modified
+ * in any way by this method; rather, the rewriter just records that a new import has been added.
+ *
+ * @param qualifiedTypeName the qualified type name of the type to be added
+ * @param context an optional context that knows about types visible in the current scope or null
+ * to use the default context only using the available imports.
+ * @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
+ * or else a qualified name if an import conflict prevented an import.
+ */
+ public String addImport(String qualifiedTypeName, ImportRewriteContext context) {
+ int angleBracketOffset= qualifiedTypeName.indexOf('<');
+ if (angleBracketOffset != -1) {
+ return internalAddImport(qualifiedTypeName.substring(0, angleBracketOffset), context) + qualifiedTypeName.substring(angleBracketOffset);
+ }
+ int bracketOffset= qualifiedTypeName.indexOf('[');
+ if (bracketOffset != -1) {
+ return internalAddImport(qualifiedTypeName.substring(0, bracketOffset), context) + qualifiedTypeName.substring(bracketOffset);
+ }
+ return internalAddImport(qualifiedTypeName, context);
+ }
+
+ /**
+ * Adds a new import to the rewriter's record and returns a type reference that can be used
+ * in the code. The type binding can only be an array or non-generic type.
+ *
+ * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
+ *
+ *
+ * The content of the compilation unit itself is actually not modified
+ * in any way by this method; rather, the rewriter just records that a new import has been added.
+ *
+ * @param qualifiedTypeName the qualified type name of the type to be added
+ * @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
+ * or else a qualified name if an import conflict prevented an import.
+ */
+ public String addImport(String qualifiedTypeName) {
+ return addImport(qualifiedTypeName, this.defaultContext);
+ }
+
+
+
+ private String internalAddImport(String fullTypeName, ImportRewriteContext context) {
+ int idx= fullTypeName.lastIndexOf('.');
+ String typeContainerName, typeName;
+ if (idx != -1) {
+ typeContainerName= fullTypeName.substring(0, idx);
+ typeName= fullTypeName.substring(idx + 1);
+ } else {
+ typeContainerName= ""; //$NON-NLS-1$
+ typeName= fullTypeName;
+ }
+
+ if (typeContainerName.length() == 0 && PrimitiveType.toCode(typeName) != null) {
+ return fullTypeName;
+ }
+
+ if (context == null)
+ context= this.defaultContext;
+
+ int res= context.findInContext(typeContainerName, typeName, ImportRewriteContext.KIND_TYPE);
+ if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
+ return fullTypeName;
+ }
+ if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
+ addEntry(NORMAL_PREFIX + fullTypeName);
+ }
+ if (res == ImportRewriteContext.RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT) {
+ addEntry(NORMAL_PREFIX + fullTypeName);
+ this.typeExplicitSimpleNames.add(typeName);
+ }
+ return typeName;
+ }
+
+ private void addEntry(String entry) {
+ this.existingImports.add(entry);
+
+ this.addedImports.add(entry);
+ }
+
+
+
+ /**
+ * Returns all non-static imports that are recorded to be added.
+ *
+ * @return the imports recorded to be added.
+ */
+ public String[] getAddedImports() {
+ return filterFromList(this.addedImports, NORMAL_PREFIX);
+ }
+
+ /**
+ * Returns true if imports have been recorded to be added or removed.
+ * @return boolean returns if any changes to imports have been recorded.
+ */
+ public boolean hasRecordedChanges() {
+ return !this.restoreExistingImports
+ || !this.addedImports.isEmpty();
+ }
+
+ private static String[] filterFromList(List imports, char prefix) {
+ if (imports == null) {
+ return CharOperation.NO_STRINGS;
+ }
+ List res= new ArrayList<>();
+ for (String curr : imports) {
+ if (prefix == curr.charAt(0)) {
+ res.add(curr.substring(1));
+ }
+ }
+ return res.toArray(new String[res.size()]);
+ }
+
+
+ /**
+ * Reads the positions of each existing import declaration along with any associated comments,
+ * and returns these in a list whose iteration order reflects the existing order of the imports
+ * in the compilation unit.
+ */
+ private int getAddedImportsInsertLocation() {
+ List importDeclarations = astRoot.imports();
+
+ if (importDeclarations == null) {
+ importDeclarations = Collections.emptyList();
+ }
+
+ List comments = astRoot.getCommentList();
+
+ int currentCommentIndex = 0;
+
+ // Skip over package and file header comments (see https://bugs.eclipse.org/121428).
+ ImportDeclaration firstImport = importDeclarations.get(0);
+ PackageDeclaration packageDeclaration = astRoot.getPackage();
+ int firstImportStartPosition = packageDeclaration == null
+ ? firstImport.getStartPosition()
+ : astRoot.getExtendedStartPosition(packageDeclaration)
+ + astRoot.getExtendedLength(packageDeclaration);
+ while (currentCommentIndex < comments.size()
+ && comments.get(currentCommentIndex).getStartPosition() < firstImportStartPosition) {
+ currentCommentIndex++;
+ }
+
+ int previousExtendedEndPosition = -1;
+ for (ImportDeclaration currentImport : importDeclarations) {
+ int extendedEndPosition = astRoot.getExtendedStartPosition(currentImport)
+ + astRoot.getExtendedLength(currentImport);
+
+ int commentAfterImportIndex = currentCommentIndex;
+ while (commentAfterImportIndex < comments.size()
+ && comments.get(commentAfterImportIndex).getStartPosition() < extendedEndPosition) {
+ commentAfterImportIndex++;
+ }
+
+
+ currentCommentIndex = commentAfterImportIndex;
+ previousExtendedEndPosition = extendedEndPosition;
+ }
+
+ return previousExtendedEndPosition;
+ }
+
+ public DocumentEdits createEdit(IDocument doc) {
+ DocumentEdits edits = null;
+ StringBuffer buffer = new StringBuffer();
+
+ String[] createdImprts = getAddedImports();
+ if (createdImprts != null && createdImprts.length >0) {
+ edits =new DocumentEdits(doc);
+ buffer.append('\n');
+ for (String imp : createdImprts) {
+ buffer.append("import ");
+ buffer.append(imp);
+ buffer.append(';');
+ buffer.append('\n');
+ }
+ edits.insert(getAddedImportsInsertLocation(), buffer.toString());
+
+ }
+ return edits;
+ }
+}
diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java
index e5da5175c..0e3fa7755 100644
--- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java
+++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java
@@ -14,6 +14,7 @@ import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
@@ -44,12 +45,13 @@ public class JavaSnippet {
}
public Optional generateCompletion(Supplier snippetBuilderFactory,
- DocumentRegion query, ASTNode node) {
+ DocumentRegion query, ASTNode node, CompilationUnit cu) {
if (context.appliesTo(node)) {
return Optional.of(
new JavaSnippetCompletion(snippetBuilderFactory,
query,
+ cu,
this
)
);
@@ -66,8 +68,8 @@ public class JavaSnippet {
return this.template;
}
- public List getImports() {
- return this.imports;
+ public Optional> getImports() {
+ return Optional.of(this.imports);
}
public CompletionItemKind getKind() {
diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java
index 266105a05..0be2d8366 100644
--- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java
+++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java
@@ -12,7 +12,9 @@ package org.springframework.ide.vscode.boot.java.snippets;
import java.util.Optional;
+import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.CompletionItemKind;
+import org.springframework.ide.vscode.boot.java.jdt.imports.ImportRewrite;
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.util.DocumentRegion;
@@ -27,10 +29,12 @@ public class JavaSnippetCompletion implements ICompletionProposal{
private DocumentRegion query;
private JavaSnippet javaSnippet;
private Supplier snippetBuilderFactory;
+ private CompilationUnit cu;
- public JavaSnippetCompletion(Supplier snippetBuilderFactory, DocumentRegion query, JavaSnippet javaSnippet) {
+ public JavaSnippetCompletion(Supplier snippetBuilderFactory, DocumentRegion query, CompilationUnit cu, JavaSnippet javaSnippet) {
this.snippetBuilderFactory = snippetBuilderFactory;
this.query = query;
+ this.cu = cu;
this.javaSnippet = javaSnippet;
}
@@ -61,8 +65,17 @@ public class JavaSnippetCompletion implements ICompletionProposal{
@Override
public Optional getAdditionalEdit() {
- DocumentEdits edit = new DocumentEdits(query.getDocument());
- edit.insert(0, "import my.stuff;\n");
- return Optional.of(edit );
+ ImportRewrite rewrite = ImportRewrite.create(cu, true);
+
+ javaSnippet.getImports().ifPresent((imprts ->
+ {
+ for (String imprt : imprts) {
+ rewrite.addImport(imprt);
+ }
+ }));
+
+ DocumentEdits edit = rewrite.createEdit(query.getDocument());
+
+ return Optional.of(edit);
}
}
diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java
index 24d16bea2..ed6f7fdc8 100644
--- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java
+++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java
@@ -15,6 +15,7 @@ import java.util.Collection;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.CompilationUnit;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder;
@@ -46,14 +47,14 @@ public class JavaSnippetManager {
}
- public Collection getCompletions(IDocument doc, int offset, ASTNode node) {
+ public Collection getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu) {
Collection completions = new ArrayList<>();
DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset);
for (JavaSnippet javaSnippet : snippets) {
if (FuzzyMatcher.matchScore(query.toString(), javaSnippet.getName()) != 0) {
- javaSnippet.generateCompletion(snippetBuilderFactory, query, node)
+ javaSnippet.generateCompletion(snippetBuilderFactory, query, node, cu)
.ifPresent((completion) -> completions.add(completion));
}
}