diff --git a/libs/natls/src/main/java/org/amshove/natls/completion/CompletionProvider.java b/libs/natls/src/main/java/org/amshove/natls/completion/CompletionProvider.java index eeb62a2cd..8ada65c88 100644 --- a/libs/natls/src/main/java/org/amshove/natls/completion/CompletionProvider.java +++ b/libs/natls/src/main/java/org/amshove/natls/completion/CompletionProvider.java @@ -3,6 +3,7 @@ import org.amshove.natls.config.LSConfiguration; import org.amshove.natls.hover.HoverContext; import org.amshove.natls.hover.HoverProvider; +import org.amshove.natls.languageserver.LspUtil; import org.amshove.natls.languageserver.UnresolvedCompletionInfo; import org.amshove.natls.project.LanguageServerFile; import org.amshove.natls.project.LanguageServerLibrary; @@ -14,6 +15,7 @@ import org.amshove.natparse.natural.builtin.SystemFunctionDefinition; import org.amshove.natparse.natural.project.NaturalFileType; import org.eclipse.lsp4j.*; +import org.eclipse.lsp4j.jsonrpc.messages.Either; import java.util.*; import java.util.logging.Level; @@ -135,6 +137,11 @@ public CompletionItem resolveComplete(CompletionItem item, LanguageServerFile ca } var module = calledModulesFile.module(); + if (module == null) + { + // Happens when the module this is called on has unrecoverable errors + return item; + } return switch (item.getKind()) { @@ -194,7 +201,16 @@ public CompletionItem resolveComplete(CompletionItem item, LanguageServerFile ca ) ); var callnat = info.hasPreviousText("CALLNAT") ? "" : "CALLNAT "; - item.setInsertText("%s'%s'%s%n$0".formatted(callnat, calledModulesFile.getReferableName(), externalModuleParameterListAsSnippet(calledModulesFile))); + if (item.getTextEdit() != null && item.getTextEdit().isLeft()) + { + item.getTextEdit().getLeft().setNewText( + "%s'%s%n$0".formatted(calledModulesFile.getReferableName(), externalModuleParameterListAsSnippet(calledModulesFile)) + ); + } + else + { + item.setInsertText("%s'%s'%s%n$0".formatted(callnat, calledModulesFile.getReferableName(), externalModuleParameterListAsSnippet(calledModulesFile))); + } yield item; } default -> item; @@ -309,6 +325,17 @@ private Collection subprogramCompletions(LanguageServe item.setData(createUnresolvedInfo(f, context)); item.setKind(CompletionItemKind.Class); item.setSortText("5"); + if (context.isCurrentTokenKind(SyntaxKind.STRING_LITERAL)) + { + item.setTextEdit( + Either.forLeft( + new TextEdit( + LspUtil.toRangeSpanning(context.originalPosition(), context.currentToken()), + "" + ) + ) + ); + } return item; } catch (Exception e) diff --git a/libs/natls/src/main/java/org/amshove/natls/languageserver/LspUtil.java b/libs/natls/src/main/java/org/amshove/natls/languageserver/LspUtil.java index b54c0adda..14258a732 100644 --- a/libs/natls/src/main/java/org/amshove/natls/languageserver/LspUtil.java +++ b/libs/natls/src/main/java/org/amshove/natls/languageserver/LspUtil.java @@ -92,6 +92,14 @@ public static Range toRange(Position position) ); } + public static Range toRangeSpanning(Position start, IPosition endOfThisPosition) + { + return new Range( + start, + toRange(endOfThisPosition).getEnd() + ); + } + public static Range toRange(IPosition position) { return new Range( diff --git a/libs/natls/src/test/java/org/amshove/natls/completion/CompletionTest.java b/libs/natls/src/test/java/org/amshove/natls/completion/CompletionTest.java index f8ed56ee3..e7c71efcb 100644 --- a/libs/natls/src/test/java/org/amshove/natls/completion/CompletionTest.java +++ b/libs/natls/src/test/java/org/amshove/natls/completion/CompletionTest.java @@ -88,10 +88,20 @@ CompletionAssertion assertContainsCompleting(String label, CompletionItemKind ki items.stream().filter(i -> i.getKind() == kind).map(Object::toString).collect(Collectors.joining("\n")) ) ) - .anyMatch(ci -> ci.getLabel().equals(label) && ci.getKind() == kind && ci.getInsertText().equals(completion)); + .anyMatch(ci -> ci.getLabel().equals(label) && ci.getKind() == kind && isCompleting(ci, completion)); return this; } + private boolean isCompleting(CompletionItem item, String expectedCompletion) + { + if (item.getInsertText() != null) + { + return item.getInsertText().equals(expectedCompletion); + } + + return item.getTextEdit().getLeft().getNewText().equals(expectedCompletion); + } + CompletionAssertion assertDoesNotContainVariable(String label) { assertThat(items) diff --git a/libs/natls/src/test/java/org/amshove/natls/completion/ExternalModuleCompletionShould.java b/libs/natls/src/test/java/org/amshove/natls/completion/ExternalModuleCompletionShould.java index a4bbfdb21..5f54fbac4 100644 --- a/libs/natls/src/test/java/org/amshove/natls/completion/ExternalModuleCompletionShould.java +++ b/libs/natls/src/test/java/org/amshove/natls/completion/ExternalModuleCompletionShould.java @@ -377,6 +377,44 @@ void completeCallnatsWhenCallnatIsAlreadyPresent() .assertContainsCompleting("SUBN", CompletionItemKind.Class, "'SUBN'%n$0".formatted()); } + @Test + void completeCallnatsWhenStringIsAlreadyPresent() + { + createOrSaveFile("LIBONE", "SUBN.NSN", """ + DEFINE DATA PARAMETER + 1 #PARAM (A10) + END-DEFINE + END + """); + + assertCompletions("LIBONE", "SUB2.NSN", """ + DEFINE DATA LOCAL + END-DEFINE + CALLNAT '${}$' + END + """) + .assertContainsCompleting("SUBN", CompletionItemKind.Class, "SUBN' ${1:#PARAM}%n$0".formatted()); + } + + @Test + void completeCallnatsWhenStringIsAlreadyPartlyPresent() + { + createOrSaveFile("LIBONE", "SUBN.NSN", """ + DEFINE DATA PARAMETER + 1 #PARAM (A10) + END-DEFINE + END + """); + + assertCompletions("LIBONE", "SUB2.NSN", """ + DEFINE DATA LOCAL + END-DEFINE + CALLNAT '${}$ + END + """) + .assertContainsCompleting("SUBN", CompletionItemKind.Class, "SUBN' ${1:#PARAM}%n$0".formatted()); + } + @Test void completeCallnatsWithParameterWhenCallnatIsAlreadyPresent() {