From cb882e8a1a4472ba1fa02927ea82891e0b00931e Mon Sep 17 00:00:00 2001 From: azerr Date: Sat, 15 Jul 2023 17:33:10 +0200 Subject: [PATCH] fix: Inserted snippets should be reindented following the IDE indentation setting (#1019) Fixes #1019 Signed-off-by: azerr --- .../completion/LSCompletionProposal.java | 5 +- .../completion/LSContentAssistProcessor.java | 16 ++- .../LSIncompleteCompletionProposal.java | 50 ++++++- .../completion/SnippetTemplateFactory.java | 5 +- .../completion/SnippetTemplateLoader.java | 8 +- .../snippet/AbstractLspSnippetHandler.java | 22 +++- .../snippet/DefaultLspSnippetHandler.java | 12 +- .../snippet/LspSnippetIndentOptions.java | 122 ++++++++++++++++++ .../completion/snippet/AdvancedTest.java | 6 +- .../ExtractSnippetLinkedPositionTest.java | 29 ++++- .../completion/snippet/LspSnippetAssert.java | 4 +- .../ExtractSnippetLinkedPositionHandler.java | 5 +- 12 files changed, 254 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/LspSnippetIndentOptions.java diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSCompletionProposal.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSCompletionProposal.java index e3864b93c..daffcd2db 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSCompletionProposal.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSCompletionProposal.java @@ -13,6 +13,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.services.LanguageServer; @@ -22,8 +23,8 @@ public class LSCompletionProposal extends LSIncompleteCompletionProposal { private static final Logger LOGGER = LoggerFactory.getLogger(LSCompletionProposal.class); - public LSCompletionProposal(Editor editor, int offset, CompletionItem item, LanguageServer languageServer) { - super(editor, offset, item, languageServer); + public LSCompletionProposal(PsiFile file, Editor editor, int offset, CompletionItem item, LanguageServer languageServer) { + super(file, editor, offset, item, languageServer); } @Override diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSContentAssistProcessor.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSContentAssistProcessor.java index 6022c793b..64a5f53ca 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSContentAssistProcessor.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSContentAssistProcessor.java @@ -22,6 +22,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; import com.redhat.devtools.intellij.lsp4ij.LanguageServerWrapper; import com.redhat.devtools.intellij.lsp4ij.LanguageServiceAccessor; @@ -51,7 +52,8 @@ public class LSContentAssistProcessor extends CompletionContributor { public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { Document document = parameters.getEditor().getDocument(); Editor editor = parameters.getEditor(); - Project project = parameters.getOriginalFile().getProject(); + PsiFile file = parameters.getOriginalFile(); + Project project = file.getProject(); int offset = parameters.getOffset(); CompletableFuture>> completionLanguageServersFuture = initiateLanguageServers(project, document); try { @@ -71,7 +73,7 @@ public void fillCompletionVariants(@NotNull CompletionParameters parameters, @No ProgressManager.checkCanceled(); Pair, CompletionList>, LanguageServer> pair = proposals.poll(25, TimeUnit.MILLISECONDS); if (pair != null) { - result.addAllElements(toProposals(project, editor, document, offset, pair.getFirst(), + result.addAllElements(toProposals(file, editor, document, offset, pair.getFirst(), pair.getSecond())); } @@ -85,14 +87,14 @@ public void fillCompletionVariants(@NotNull CompletionParameters parameters, @No super.fillCompletionVariants(parameters, result); } - private Collection toProposals(Project project, Editor editor, Document document, + private Collection toProposals(PsiFile file, Editor editor, Document document, int offset, Either, CompletionList> completion, LanguageServer languageServer) { if (completion != null) { List items = completion.isLeft() ? completion.getLeft() : completion.getRight().getItems(); boolean isIncomplete = completion.isRight() && completion.getRight().isIncomplete(); return items.stream() - .map(item -> createLookupItem(editor, offset, item, isIncomplete, languageServer)) + .map(item -> createLookupItem(file, editor, offset, item, isIncomplete, languageServer)) .filter(item -> item.validate(document, offset, null)) .map(item -> PrioritizedLookupElement.withGrouping(item, item.getItem().getKind().getValue())) .collect(Collectors.toList()); @@ -100,11 +102,11 @@ private Collection toProposals(Project project, Editor return Collections.emptyList(); } - private LSIncompleteCompletionProposal createLookupItem(Editor editor, int offset, + private LSIncompleteCompletionProposal createLookupItem(PsiFile file, Editor editor, int offset, CompletionItem item, boolean isIncomplete, LanguageServer languageServer) { - return isIncomplete ? new LSIncompleteCompletionProposal(editor, offset, item, languageServer) : - new LSCompletionProposal(editor, offset, item, languageServer); + return isIncomplete ? new LSIncompleteCompletionProposal(file, editor, offset, item, languageServer) : + new LSCompletionProposal(file, editor, offset, item, languageServer); } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSIncompleteCompletionProposal.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSIncompleteCompletionProposal.java index 69b2e540b..3c1c25313 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSIncompleteCompletionProposal.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSIncompleteCompletionProposal.java @@ -18,14 +18,23 @@ import com.intellij.codeInsight.template.Template; import com.intellij.codeInsight.template.TemplateManager; import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.lang.Language; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorModificationUtil; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.psi.impl.PsiManagerEx; +import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider; import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; import com.redhat.devtools.intellij.lsp4ij.LanguageServiceAccessor; import com.redhat.devtools.intellij.lsp4ij.command.internal.CommandExecutor; +import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetIndentOptions; import org.apache.commons.lang.StringUtils; import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -47,6 +56,7 @@ public class LSIncompleteCompletionProposal extends LookupElement { protected final CompletionItem item; protected final int initialOffset; + private final PsiFile file; protected int currentOffset; protected int bestOffset; protected final Editor editor; @@ -56,7 +66,8 @@ public class LSIncompleteCompletionProposal extends LookupElement { private String documentFilterAddition = ""; //$NON-NLS-1$ protected final LanguageServer languageServer; - public LSIncompleteCompletionProposal(Editor editor, int offset, CompletionItem item, LanguageServer languageServer) { + public LSIncompleteCompletionProposal(PsiFile file, Editor editor, int offset, CompletionItem item, LanguageServer languageServer) { + this.file = file; this.item = item; this.editor = editor; this.languageServer = languageServer; @@ -72,15 +83,18 @@ public void handleInsert(@NotNull InsertionContext context) { if (item.getInsertTextFormat() == InsertTextFormat.Snippet) { // Insert text has snippet syntax, ex : ${1:name} String insertText = getInsertText(); + // Get the indentation settings + LspSnippetIndentOptions indentOptions = createLspIndentOptions(insertText, file); // Load the insert text to build: // - an IJ Template instance which will take care of replacement of placeholders // - the insert text without placeholders - template = SnippetTemplateFactory.createTemplate(insertText, context.getProject(), name -> getVariableValue(name)); + template = SnippetTemplateFactory.createTemplate(insertText, context.getProject(), name -> getVariableValue(name), indentOptions); // Update the TextEdit with the content snippet content without placeholders // ex : ${1:name} --> name updateInsertText(template.getTemplateText()); } + // Apply all text edits apply(context.getDocument(), context.getCompletionChar(), 0, context.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET)); if (template != null && ((TemplateImpl) template).getVariableCount() > 0) { @@ -91,6 +105,38 @@ public void handleInsert(@NotNull InsertionContext context) { } } + private static LspSnippetIndentOptions createLspIndentOptions(String insertText, PsiFile file) { + if (LspSnippetIndentOptions.shouldBeFormatted(insertText)) { + // Get global line separator settings + CodeStyleSettings settings = CodeStyleSettings.getDefaults(); + String lineSeparator = settings.getLineSeparator(); + CommonCodeStyleSettings.@NotNull IndentOptions indentOptions = getIndentOptions(file, settings); + boolean insertSpaces = !indentOptions.USE_TAB_CHARACTER; + int tabSize = indentOptions.TAB_SIZE; + return new LspSnippetIndentOptions(tabSize, insertSpaces, lineSeparator); + } + return null; + } + + private static CommonCodeStyleSettings.@NotNull IndentOptions getIndentOptions(PsiFile file, CodeStyleSettings settings) { + Project project = file.getProject(); + FileViewProvider provider = PsiManagerEx.getInstanceEx(project).findViewProvider(file.getVirtualFile()); + if (provider instanceof TemplateLanguageFileViewProvider) { + // get indent options of the language + Language language = ((TemplateLanguageFileViewProvider) provider).getTemplateDataLanguage(); + CommonCodeStyleSettings.IndentOptions indentOptions = settings.getLanguageIndentOptions(language); + if (indentOptions != null) { + return indentOptions; + } + } + Language language = provider.getBaseLanguage(); + CommonCodeStyleSettings.IndentOptions indentOptions = settings.getLanguageIndentOptions(language); + if (indentOptions != null) { + return indentOptions; + } + return settings.getIndentOptions(); + } + /** * See {@link CompletionProposalTools.getFilterFromDocument} for filter * generation logic diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/SnippetTemplateFactory.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/SnippetTemplateFactory.java index 836a7dc8f..55059f85c 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/SnippetTemplateFactory.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/SnippetTemplateFactory.java @@ -16,6 +16,7 @@ import com.intellij.codeInsight.template.Template; import com.intellij.codeInsight.template.TemplateManager; import com.intellij.openapi.project.Project; +import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetIndentOptions; import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetParser; import org.jetbrains.annotations.NotNull; @@ -37,10 +38,10 @@ public class SnippetTemplateFactory { * @param variableResolver the LSP variable resolvers (ex to resolve ${TM_SELECTED_TEXT}). * @return an Intellij Template instanced loaded from the given LSP snippet content. */ - public static @NotNull Template createTemplate(@NotNull String snippetContent, @NotNull Project project, @NotNull Function variableResolver) { + public static @NotNull Template createTemplate(@NotNull String snippetContent, @NotNull Project project, @NotNull Function variableResolver, LspSnippetIndentOptions indentOptions) { Template template = TemplateManager.getInstance(project).createTemplate("", ""); template.setInline(true); - LspSnippetParser parser = new LspSnippetParser(new SnippetTemplateLoader(template, variableResolver)); + LspSnippetParser parser = new LspSnippetParser(new SnippetTemplateLoader(template, variableResolver, indentOptions)); parser.parse(snippetContent); return template; } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/SnippetTemplateLoader.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/SnippetTemplateLoader.java index 31571b44d..00da14871 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/SnippetTemplateLoader.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/SnippetTemplateLoader.java @@ -17,6 +17,7 @@ import com.intellij.codeInsight.template.impl.ConstantNode; import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.AbstractLspSnippetHandler; import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetHandler; +import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetIndentOptions; import java.util.ArrayList; import java.util.List; @@ -39,9 +40,10 @@ public class SnippetTemplateLoader extends AbstractLspSnippetHandler { * * @param template the Intellij template to load. * @param variableResolver the variable resolver (ex : resolve value of TM_SELECTED_TEXT when snippet declares ${TM_SELECTED_TEXT}) + * @param indentOptions the LSP indent options to replace '\t' and '\n' characters according the code style settings of the language. */ - public SnippetTemplateLoader(Template template, Function variableResolver) { - super(variableResolver); + public SnippetTemplateLoader(Template template, Function variableResolver, LspSnippetIndentOptions indentOptions) { + super(variableResolver, indentOptions); this.template = template; this.existingVariables = new ArrayList<>(); } @@ -58,7 +60,7 @@ public void endSnippet() { @Override public void text(String text) { - template.addTextSegment(text); + template.addTextSegment(formatText(text)); } @Override diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/AbstractLspSnippetHandler.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/AbstractLspSnippetHandler.java index 6e93ce1af..b1be57d5d 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/AbstractLspSnippetHandler.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/AbstractLspSnippetHandler.java @@ -25,8 +25,28 @@ public abstract class AbstractLspSnippetHandler implements LspSnippetHandler { private final Function variableResolver; - public AbstractLspSnippetHandler(Function variableResolver) { + private final LspSnippetIndentOptions indentOptions; + + /** + * Abstract LSP snippet handler constructor. + * + * @param variableResolver the variable resolver. + * @param indentOptions the indent options to use to format text block which contains '\n' and '\t'. + */ + public AbstractLspSnippetHandler(Function variableResolver, LspSnippetIndentOptions indentOptions) { this.variableResolver = variableResolver; + this.indentOptions = indentOptions; + } + + /** + * Replace '\n' and '\t' declared in the snippets according to the LSP client settings. + * + * @param text the text to format according to the LSP client settings. + * + * @return the result of '\n' and '\t' replacement declared in the snippets according to the LSP client settings. + */ + protected String formatText(String text) { + return indentOptions != null ? indentOptions.formatText(text) : text; } /** diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/DefaultLspSnippetHandler.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/DefaultLspSnippetHandler.java index 0bf82821d..0c23723da 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/DefaultLspSnippetHandler.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/DefaultLspSnippetHandler.java @@ -30,8 +30,14 @@ public class DefaultLspSnippetHandler extends AbstractLspSnippetHandler { private final StringBuilder templateContent; - public DefaultLspSnippetHandler(Function variableResolver) { - super(variableResolver); + /** + * Default LSP snippet handler constructor. + * + * @param variableResolver the variable resolver. + * @param indentOptions the indent options to use to format text block which contains '\n' and '\t'. + */ + public DefaultLspSnippetHandler(Function variableResolver, LspSnippetIndentOptions indentOptions) { + super(variableResolver, indentOptions); this.templateContent = new StringBuilder(); } @@ -47,7 +53,7 @@ public void endSnippet() { @Override public void text(String text) { - appendContent(text); + appendContent(formatText(text)); } @Override diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/LspSnippetIndentOptions.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/LspSnippetIndentOptions.java new file mode 100644 index 000000000..735950a3a --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/LspSnippetIndentOptions.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet; + +/** + * LSP snippet indent options used to replace LSP snippet content '\n' and '\t: + * + *
    + *
  • '\n' with the line separator settings of the LSP client.
  • + *
  • '\t' with the tab size settings of the LSP client if the LSP client settings has insert spaces.
  • + *
+ */ +public class LspSnippetIndentOptions { + + private static final String CRLF = "\r\n"; + + private final int tabSize; + + private final boolean insertSpaces; + + private final String lineSeparator; + + private String replacementTab; + + public LspSnippetIndentOptions(int tabSize, boolean insertSpaces, String lineSeparator) { + this.tabSize = tabSize; + this.insertSpaces = insertSpaces; + this.lineSeparator = lineSeparator; + } + + public int getTabSize() { + return tabSize; + } + + public boolean isInsertSpaces() { + return insertSpaces; + } + + public String getLineSeparator() { + return lineSeparator; + } + + /** + * Replace '\n' and '\t' declared in the snippets according to the LSP client settings. + * + * @param text the text to format according to the LSP client settings. + * + * @return the result of '\n' and '\t' replacement declared in the snippets according to the LSP client settings. + */ + protected String formatText(String text) { + if (!shouldBeFormatted(text)) { + return text; + } + StringBuilder formattedText = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + switch (c) { + case '\t': + if (!insertSpaces) { + formattedText.append(c); + } else { + formattedText.append(getSpacesReplacement()); + } + break; + case '\n': + if (shouldIgnoreLineSeparatorSettings(text, i)) { + formattedText.append(c); + } else { + formattedText.append(lineSeparator); + } + break; + default: + formattedText.append(c); + } + } + return formattedText.toString(); + } + + private boolean shouldIgnoreLineSeparatorSettings(String text, int i) { + if (lineSeparator == null || lineSeparator.isEmpty()) { + // Line separator is not customized + return true; + } + if (lineSeparator.equals(CRLF)) { + // Line separator settings is '\r\n', check that the previous character of the text is not '\r' + return i >= 1 && text.charAt(i-1) == '\r'; + } + return false; + } + + public String getSpacesReplacement() { + if(replacementTab == null) { + StringBuilder spaces = new StringBuilder(); + for (int i = 0; i < tabSize; i++) { + spaces.append(' '); + } + replacementTab = spaces.toString(); + } + return replacementTab; + } + + /** + * Returns true if the given text contains '\n' or '\t' and false otherwise. + * @param text the text to format according the LSP client settings. + * @return true if the given text contains '\n' or '\t' and false otherwise. + */ + public static boolean shouldBeFormatted(String text) { + return text.indexOf('\t') != -1 || text.indexOf('\n') != -1; + } + +} diff --git a/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/AdvancedTest.java b/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/AdvancedTest.java index cae42e44b..9076f8230 100644 --- a/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/AdvancedTest.java +++ b/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/AdvancedTest.java @@ -22,18 +22,18 @@ public class AdvancedTest { @Test public void textAndlaceholdersAndTabStop() { - LspSnippetNode[] actual = parse("{#for ${1:item} in ${2:items}}\\r\\n\\t{${1:item}.${3:name}}$0\\r\\n{/for}"); + LspSnippetNode[] actual = parse("{#for ${1:item} in ${2:items}}\n\t{${1:item}.${3:name}}$0\n{/for}"); assertEquals(actual, text("{#for "), // placeholder(1, "item", 1), // ${1:item} text(" in "), // placeholder(2, "items", 1), // ${2:items} - text("}\\r\\n\\t{"), // + text("}\n\t{"), // placeholder(1, "item", 1), // ${1:item} text("."), // placeholder(3, "name", 1), // ${3:name} text("}"), // tabstop(0), // - text("\\r\\n{/for}")); + text("\n{/for}")); } } diff --git a/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/ExtractSnippetLinkedPositionTest.java b/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/ExtractSnippetLinkedPositionTest.java index ccbc48a39..60388255a 100644 --- a/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/ExtractSnippetLinkedPositionTest.java +++ b/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/ExtractSnippetLinkedPositionTest.java @@ -23,12 +23,35 @@ public class ExtractSnippetLinkedPositionTest { @Test public void linkedPositions() { LinkedPositionResult actual = parseLinkedPosition( - "{#for ${1:item} in ${2:items}}\\r\\n\\t{${1:item}.${3:name}}$0\\r\\n{/for}"); - assertEquals(actual, "{#for item in items}\\r\\n\\t{item.name}\\r\\n{/for}", // + "{#for ${1:item} in ${2:items}}\n\t{${1:item}.${3:name}}$0\n{/for}", null); + assertEquals(actual, "{#for item in items}\n\t{item.name}\n{/for}", // + position("item", 6, 4), // + position("items", 14, 5), // + position("item", 23, 4), // + position("name", 28, 4)); + } + + @Test + public void linkedPositionsWithIndentOptions() { + LspSnippetIndentOptions indentOptions = new LspSnippetIndentOptions(4, true, "\r\n"); + LinkedPositionResult actual = parseLinkedPosition( + "{#for ${1:item} in ${2:items}}\n\t{${1:item}.${3:name}}$0\n{/for}", indentOptions); + assertEquals(actual, "{#for item in items}\r\n {item.name}\r\n{/for}", // + position("item", 6, 4), // + position("items", 14, 5), // + position("item", 27, 4), // + position("name", 32, 4)); + } + + @Test + public void linkedPositionsWithIndentOptionsAndCLRF() { + LspSnippetIndentOptions indentOptions = new LspSnippetIndentOptions(4, true, "\r\n"); + LinkedPositionResult actual = parseLinkedPosition( + "{#for ${1:item} in ${2:items}}\r\n\t{${1:item}.${3:name}}$0\r\n{/for}", indentOptions); + assertEquals(actual, "{#for item in items}\r\n {item.name}\r\n{/for}", // position("item", 6, 4), // position("items", 14, 5), // position("item", 27, 4), // position("name", 32, 4)); } - } diff --git a/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/LspSnippetAssert.java b/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/LspSnippetAssert.java index 8524a623d..f8b467f3f 100644 --- a/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/LspSnippetAssert.java +++ b/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/LspSnippetAssert.java @@ -34,8 +34,8 @@ public static LspSnippetNode[] parse(String snippet) { return handler.getNodes(); } - public static LinkedPositionResult parseLinkedPosition(String snippet) { - ExtractSnippetLinkedPositionHandler handler = new ExtractSnippetLinkedPositionHandler(); + public static LinkedPositionResult parseLinkedPosition(String snippet, LspSnippetIndentOptions indentOptions) { + ExtractSnippetLinkedPositionHandler handler = new ExtractSnippetLinkedPositionHandler(indentOptions); LspSnippetParser parser = new LspSnippetParser(handler); parser.parse(snippet); return handler.getResult(); diff --git a/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/handler/ExtractSnippetLinkedPositionHandler.java b/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/handler/ExtractSnippetLinkedPositionHandler.java index 2f3918238..c69029514 100644 --- a/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/handler/ExtractSnippetLinkedPositionHandler.java +++ b/src/test/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/snippet/handler/ExtractSnippetLinkedPositionHandler.java @@ -14,6 +14,7 @@ package com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.handler; import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.DefaultLspSnippetHandler; +import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetIndentOptions; import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetVariableConstants; import java.util.ArrayList; @@ -23,14 +24,14 @@ public class ExtractSnippetLinkedPositionHandler extends DefaultLspSnippetHandle private final List linkedPositions; - public ExtractSnippetLinkedPositionHandler() { + public ExtractSnippetLinkedPositionHandler(LspSnippetIndentOptions indentOptions) { super(name -> { switch (name) { case LspSnippetVariableConstants.TM_FILENAME: return "foo.txt"; } return name; - }); + }, indentOptions); this.linkedPositions = new ArrayList<>(); }