diff --git a/src/main/java/com/github/cameltooling/lsp/internal/CamelTextDocumentService.java b/src/main/java/com/github/cameltooling/lsp/internal/CamelTextDocumentService.java index 2d4f9fce..0dccc2e4 100644 --- a/src/main/java/com/github/cameltooling/lsp/internal/CamelTextDocumentService.java +++ b/src/main/java/com/github/cameltooling/lsp/internal/CamelTextDocumentService.java @@ -19,11 +19,14 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import com.github.cameltooling.lsp.internal.completion.modeline.CamelKModelineInsertionProcessor; +import com.github.cameltooling.lsp.internal.parser.CamelEIPParser; import com.github.cameltooling.lsp.internal.parser.CamelKModelineInsertionParser; import org.apache.camel.catalog.CamelCatalog; import org.apache.camel.catalog.DefaultCamelCatalog; @@ -161,15 +164,34 @@ public CompletableFuture, CompletionList>> completio TextDocumentItem textDocumentItem = openedDocuments.get(uri); if (textDocumentItem != null) { - if (uri.endsWith(".properties")){ + if (uri.endsWith(".properties")) { return new CamelPropertiesCompletionProcessor(textDocumentItem, getCamelCatalog(), getCamelKafkaConnectorManager()).getCompletions(completionParams.getPosition(), getSettingsManager(), getKameletsCatalogManager()).thenApply(Either::forLeft); - } else if (new CamelKModelineInsertionParser(textDocumentItem).canPutCamelKModeline(completionParams.getPosition())){ - return new CamelKModelineInsertionProcessor(textDocumentItem).getCompletions().thenApply(Either::forLeft); + } + + List>> completions = new LinkedList<>(); + + if (new CamelEIPParser(textDocumentItem).canPutEIP(completionParams.getPosition())){ + completions.add(new CamelEIPParser(textDocumentItem).getCompletions()); + } + + if (new CamelKModelineInsertionParser(textDocumentItem).canPutCamelKModeline(completionParams.getPosition())){ + completions.add(new CamelKModelineInsertionProcessor(textDocumentItem).getCompletions()); } else if (new CamelKModelineParser().isOnCamelKModeline(completionParams.getPosition().getLine(), textDocumentItem)){ - return new CamelKModelineCompletionprocessor(textDocumentItem, getCamelCatalog()).getCompletions(completionParams.getPosition()).thenApply(Either::forLeft); + completions.add(new CamelKModelineCompletionprocessor(textDocumentItem, getCamelCatalog()).getCompletions(completionParams.getPosition())); } else { - return new CamelEndpointCompletionProcessor(textDocumentItem, getCamelCatalog(), getKameletsCatalogManager()).getCompletions(completionParams.getPosition(), getSettingsManager()).thenApply(Either::forLeft); + completions.add(new CamelEndpointCompletionProcessor(textDocumentItem, getCamelCatalog(), getKameletsCatalogManager()).getCompletions(completionParams.getPosition(), getSettingsManager())); } + + //Combining all completable futures completion lists + //Source: https://nurkiewicz.com/2013/05/java-8-completablefuture-in-action.html + return CompletableFuture + .allOf(completions.toArray(new CompletableFuture[completions.size()])) + .thenApply( + v -> completions.stream() + .map(future -> future.join()) + .flatMap(x -> x.stream()) + .collect(Collectors.toList()) + ).thenApply(Either::forLeft); } else { LOGGER.warn("The document with uri {} has not been found in opened documents. Cannot provide completion.", uri); return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); diff --git a/src/main/java/com/github/cameltooling/lsp/internal/parser/CamelEIPParser.java b/src/main/java/com/github/cameltooling/lsp/internal/parser/CamelEIPParser.java new file mode 100644 index 00000000..dc67774a --- /dev/null +++ b/src/main/java/com/github/cameltooling/lsp/internal/parser/CamelEIPParser.java @@ -0,0 +1,52 @@ +package com.github.cameltooling.lsp.internal.parser; + +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentItem; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Pattern; + +public class CamelEIPParser { + + private static final String LATEST_LTS_URL = "https://camel.apache.org/components/3.20.x"; + private final TextDocumentItem document; + + public CamelEIPParser(TextDocumentItem textDocumentItem) { + this.document = textDocumentItem; + } + + public boolean canPutEIP(Position position) { + Pattern choiceEipPattern = Pattern.compile("\\)\\s*[.[c[h[o[i[c[e]?]?]?]?]?]?]?\\(?$", Pattern.MULTILINE); + ParserFileHelperUtil util = new ParserFileHelperUtil(); + String textUntilPosition = util.getTextUntilPosition(document, position); + + return choiceEipPattern.matcher(textUntilPosition).find(); + } + + public CompletableFuture> getCompletions() { + return CompletableFuture.completedFuture( + List.of(choiceEIPcompletion()) + ); + } + + private CompletionItem choiceEIPcompletion() { + String newLine = "\n"; + CompletionItem completion = new CompletionItem("Content Based Router"); + completion.setDocumentation( + "Read more: "+LATEST_LTS_URL+"/eips/choice-eip.html" + ); + completion.setInsertText( + ".choice()" + newLine + + ".when()" + newLine + + ".to()" + newLine + + ".when()" + newLine + + ".to()" + newLine + + ".otherwise()" + newLine + + ".to()" + ); + + return completion; + } +} diff --git a/src/main/java/com/github/cameltooling/lsp/internal/parser/ParserFileHelperUtil.java b/src/main/java/com/github/cameltooling/lsp/internal/parser/ParserFileHelperUtil.java index b35de381..7fe1a128 100644 --- a/src/main/java/com/github/cameltooling/lsp/internal/parser/ParserFileHelperUtil.java +++ b/src/main/java/com/github/cameltooling/lsp/internal/parser/ParserFileHelperUtil.java @@ -19,6 +19,8 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.TextDocumentItem; +import java.util.regex.Pattern; + public class ParserFileHelperUtil { public String getLine(TextDocumentItem textDocumentItem, Position position) { @@ -36,5 +38,23 @@ public String getLine(String text, int line) { } return null; } + + public String getTextUntilPosition(TextDocumentItem document, Position position) { + String newLine = "\n"; + String[] lines = document.getText().split(newLine); + StringBuilder textUntilPosition = new StringBuilder(""); + + for(int i = 0; i completionItems = getCompletionsFor(contents, position); + completionItems = getContentBasedRouterCompletionItems(completionItems); + + assertThat(completionItems).isEmpty(); + } + + @Test + void testProvideInsertionOnEmptyJavaClass() throws Exception { + RouteTextBuilder.DocumentContentWithPosition document = + RouteTextBuilder.createJavaDocumentClass(""); + + List completionItems = getCompletionsFor(document.getContent(), document.getPosition()); + completionItems = getContentBasedRouterCompletionItems(completionItems); + + + assertThat(completionItems).isEmpty(); + } + + @Test + void testProvideInsertionOnEmptyJavaCamelRoute() throws Exception { + RouteTextBuilder.DocumentContentWithPosition document = + RouteTextBuilder.createJavaDocumentCamelRoute(""); + + List completionItems = getCompletionsFor(document.getContent(), document.getPosition()); + completionItems = getContentBasedRouterCompletionItems(completionItems); + + assertThat(completionItems).hasSize(0); + } + + @Test + void testProvideInsertionAfterFromOnCamelRoute() throws Exception { + RouteTextBuilder.DocumentContentWithPosition document = + RouteTextBuilder.createJavaDocumentCamelRoute(FROM_ROUTE); + + List completionItems = getCompletionsFor(document.getContent(), document.getPosition()); + completionItems = getContentBasedRouterCompletionItems(completionItems); + + + assertThat(completionItems).hasSize(1); + } + + @Test + void testProvideInsertionAfterFromOnCamelRouteWithLineBreaks() throws Exception { + RouteTextBuilder.DocumentContentWithPosition document = + RouteTextBuilder.createJavaDocumentCamelRoute(FROM_ROUTE_WITH_LINE_BREAKS_AND_TABS); + + List completionItems = getCompletionsFor(document.getContent(), document.getPosition()); + completionItems = getContentBasedRouterCompletionItems(completionItems); + + + assertThat(completionItems).hasSize(1); + } + + @Test + void testProvideInsertionMidWritingChoice() throws Exception { + RouteTextBuilder.DocumentContentWithPosition document = + RouteTextBuilder.createJavaDocumentCamelRoute(FROM_ROUTE_WITH_CHOICE_EIP_MID_WRITTEN); + + List completionItems = getCompletionsFor(document.getContent(), document.getPosition()); + completionItems = getContentBasedRouterCompletionItems(completionItems); + + + assertThat(completionItems).hasSize(1); + } + + + private List getCompletionsFor(String contents, Position position) throws Exception { + CamelLanguageServer camelLanguageServer = initializeLanguageServer(contents, ".java"); + + CompletableFuture, CompletionList>> completions = getCompletionFor( + camelLanguageServer, position); + + return completions.get().getLeft(); + } + + @NotNull + private List getContentBasedRouterCompletionItems(List completionItems) { + + return completionItems.stream().filter( + completionItem -> completionItem.getLabel().startsWith(CONTENT_BASED_ROUTER_LABEL) + ).collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/github/cameltooling/lsp/internal/util/RouteTextBuilder.java b/src/test/java/com/github/cameltooling/lsp/internal/util/RouteTextBuilder.java index 052c5edb..8e899832 100644 --- a/src/test/java/com/github/cameltooling/lsp/internal/util/RouteTextBuilder.java +++ b/src/test/java/com/github/cameltooling/lsp/internal/util/RouteTextBuilder.java @@ -16,11 +16,23 @@ */ package com.github.cameltooling.lsp.internal.util; +import org.eclipse.lsp4j.Position; + public class RouteTextBuilder { public static final String XML_PREFIX_FROM = "\n"; private static final String XML_SUFFIX_FROM_BLUEPRINT = "\" xmlns=\"http://camel.apache.org/schema/blueprint\"/>\n"; + + private static final String CAMEL_ROUTEBUILDER_IMPORT + = "import org.apache.camel.builder.RouteBuilder;"; + + private static final String CLASS_DECLARATION = "public class TestRoute"; + + private static final String CAMEL_ROUTEBUILDER_EXTEMD = "extends RouteBuilder"; + + private static final String CONFIGURE_METHOD_DECLARATION = "public void configure()"; + /** * @param camelUri @@ -38,4 +50,81 @@ public static String createXMLBlueprintRoute(String camelUri) { return XML_PREFIX_FROM + camelUri + XML_SUFFIX_FROM_BLUEPRINT; } + /** + * @param javaClassContent + * @return builds an empty Java class with the specified content and the cursor position inside and after + * contents + */ + public static DocumentContentWithPosition createJavaDocumentClass(String javaClassContent) { + String newLine = "\n"; + String[] contentSplit = javaClassContent.split(newLine); + int lineOffset = contentSplit.length; + int characterOffset = contentSplit[contentSplit.length-1].length(); + + if (javaClassContent.startsWith(newLine)) { + lineOffset += 1; + } + + if (javaClassContent.endsWith(newLine)) { + lineOffset +=1; + characterOffset = 0; + } + + return new DocumentContentWithPosition( + CLASS_DECLARATION + newLine + + "{" + newLine + + javaClassContent + newLine + + "}" + newLine + , 2+lineOffset, characterOffset); + } + + /** + * @param camelRoute + * @return builds an empty Java class with the specified content and the cursor position placed inside configure + * method and after content. + */ + public static DocumentContentWithPosition createJavaDocumentCamelRoute(String camelRoute) { + String newLine = "\n"; + String[] contentSplit = camelRoute.split(newLine); + int lineOffset = contentSplit.length - 1; + int characterOffset = contentSplit[contentSplit.length-1].length(); + + if (camelRoute.startsWith(newLine)) { + lineOffset += 1; + } + + if (camelRoute.endsWith(newLine)) { + lineOffset +=1; + characterOffset = 0; + } + + return new DocumentContentWithPosition( + CAMEL_ROUTEBUILDER_IMPORT + newLine + + CLASS_DECLARATION + newLine + + CAMEL_ROUTEBUILDER_EXTEMD + newLine + + "{" + newLine + + CONFIGURE_METHOD_DECLARATION + "{" + newLine + + camelRoute + newLine + + "}" + newLine + + "}" + newLine + , 5 + lineOffset,characterOffset); + } + + public static class DocumentContentWithPosition { + public String content; + public Position position; + + public DocumentContentWithPosition(String content, int line, int character) { + this.content = content; + this.position = new Position(line, character); + } + + public String getContent() { + return content; + } + + public Position getPosition() { + return position; + } + } }