diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java index 9be93138a..9dfafd966 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetBuilder.java @@ -10,25 +10,58 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.snippets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; import com.google.common.base.Supplier; +/** + * Respobsible for converting eclipse-like template string into lsp snippet text. + * @author Kris De Volder + */ public class JavaSnippetBuilder{ private Supplier snippetBuilderFactory; + private static final Pattern PLACE_HOLDER = Pattern.compile("\\$\\{(.+?)\\}"); + public JavaSnippetBuilder(Supplier snippetBuilderFactory) { this.snippetBuilderFactory = snippetBuilderFactory; } public DocumentEdits createEdit(DocumentRegion query, String template) { DocumentEdits edit = new DocumentEdits(query.getDocument()); - - edit.replace(query.getStart(), query.getEnd(), template); + edit.replace(query.getStart(), query.getEnd(), createSnippet(template)); return edit; } + private String createSnippet(String template) { + Matcher matcher = PLACE_HOLDER.matcher(template); + int start = 0; + SnippetBuilder snippet = snippetBuilderFactory.get(); + while (matcher.find(start)) { + int matchStart = matcher.start(); + snippet.text(template.substring(start, matchStart)); + int matchEnd = matcher.end(); + String placeHolderImage = template.substring(matcher.start(1), matcher.end(1)); + int colon = placeHolderImage.indexOf(':'); + String id, value; + if (colon>=0) { + id = placeHolderImage.substring(0, colon); + value = placeHolderImage.substring(colon+1); + } else { + id = placeHolderImage; + value = id; + } + snippet.placeHolder(id, value); + start = matchEnd; + } + snippet.text(template.substring(start)); + return snippet.build().toString(); + } + } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/PlaceHolderString.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/PlaceHolderString.java index c79aa0615..00546da52 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/PlaceHolderString.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/PlaceHolderString.java @@ -10,12 +10,12 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.util; -import java.util.Map; - import org.springframework.ide.vscode.commons.util.CollectionUtil; import org.springframework.ide.vscode.commons.util.text.IRegion; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; /** * Represents a string with placeholder inside. Provides methods to retrieve @@ -68,12 +68,12 @@ public class PlaceHolderString { } - private final ImmutableMap placeHolders; + private final ImmutableMultimap placeHolders; private final String string; - public PlaceHolderString(Map placeHolders, String string) { + public PlaceHolderString(Multimap placeHolders, String string) { super(); - this.placeHolders = ImmutableMap.copyOf(placeHolders); + this.placeHolders = ImmutableMultimap.copyOf(placeHolders); this.string = string; } @@ -90,7 +90,11 @@ public class PlaceHolderString { } public PlaceHolder getPlaceHolder(Object id) { - return placeHolders.get(id); + ImmutableCollection all = placeHolders.get(id); + if (all!=null && !all.isEmpty()) { + return all.iterator().next(); + } + return null; } } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SnippetBuilder.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SnippetBuilder.java index 5d5dc1aa7..5796ec14c 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SnippetBuilder.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SnippetBuilder.java @@ -11,10 +11,16 @@ package org.springframework.ide.vscode.commons.languageserver.util; import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import org.springframework.ide.vscode.commons.languageserver.util.PlaceHolderString.PlaceHolder; +import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.text.Region; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; + public class SnippetBuilder { /** @@ -25,7 +31,10 @@ public class SnippetBuilder { public static SnippetBuilder gimped() { return new SnippetBuilder() { @Override - protected String createPlaceHolder(int id) { + protected String createPlaceHolder(Object id, Optional value) { + if (value.isPresent()) { + return value.get(); + } return ""; } }; @@ -34,7 +43,9 @@ public class SnippetBuilder { private static final int FIRST_PLACE_HOLDER_ID = 1; private int nextPlaceHolderId = FIRST_PLACE_HOLDER_ID; private StringBuilder buf = new StringBuilder(); - private HashMap placeHolders = new HashMap<>(); + private Multimap placeHolders = MultimapBuilder.hashKeys().arrayListValues().build(); + + private Map idMap = new HashMap<>(); public SnippetBuilder text(String text) { buf.append(text); @@ -47,7 +58,29 @@ public class SnippetBuilder { public SnippetBuilder placeHolder() { int offset = buf.length(); int id = nextPlaceHolderId++; - buf.append(createPlaceHolder(id)); + buf.append(createPlaceHolder(id, Optional.empty())); + int end = buf.length(); + placeHolders.put(id, new PlaceHolderString.PlaceHolder(id, new Region(offset, end-offset))); + return this; + } + + public SnippetBuilder placeHolder(String name, String _value) { + Assert.isNotNull(_value); + int offset = buf.length(); + Object id; + Optional value; + if (name.equals("cursor")) { + id = 0; + value = Optional.empty(); + } else { + id = idMap.get(name); + if (id==null) { + id = nextPlaceHolderId++; + idMap.put(name, id); + } + value = Optional.of(_value); + } + buf.append(createPlaceHolder(id, value)); int end = buf.length(); placeHolders.put(id, new PlaceHolderString.PlaceHolder(id, new Region(offset, end-offset))); return this; @@ -62,8 +95,12 @@ public class SnippetBuilder { * The default implementation creates place holder strings that * match format specified by LSP 3.0. */ - protected String createPlaceHolder(int id) { - return "$"+id; + protected String createPlaceHolder(Object id, Optional value) { + if (!value.isPresent()) { + return "$"+id; + } else { + return "${"+id+":"+value.get()+"}"; + } } public PlaceHolderString build() { @@ -74,7 +111,7 @@ public class SnippetBuilder { public String toString() { String str = buf.toString(); if (getPlaceholderCount()==1 ) { - String placeHolder = createPlaceHolder(FIRST_PLACE_HOLDER_ID); + String placeHolder = createPlaceHolder(FIRST_PLACE_HOLDER_ID, Optional.empty()); if (str.endsWith(placeHolder)) { str = str.substring(0, str.length()-placeHolder.length()); } @@ -99,7 +136,7 @@ public class SnippetBuilder { * @return The number of placeholder that where inserted in the snippet. */ public int getPlaceholderCount() { - return nextPlaceHolderId-1; + return placeHolders.size(); } }