Skip to content

Commit

Permalink
fix: Inserted snippets should be reindented following the IDE
Browse files Browse the repository at this point in the history
indentation setting (#1019)

Fixes #1019

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jul 15, 2023
1 parent 48d1213 commit cb882e8
Show file tree
Hide file tree
Showing 12 changed files with 254 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<List<Pair<LanguageServerWrapper, LanguageServer>>> completionLanguageServersFuture = initiateLanguageServers(project, document);
try {
Expand All @@ -71,7 +73,7 @@ public void fillCompletionVariants(@NotNull CompletionParameters parameters, @No
ProgressManager.checkCanceled();
Pair<Either<List<CompletionItem>, 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()));
}

Expand All @@ -85,26 +87,26 @@ public void fillCompletionVariants(@NotNull CompletionParameters parameters, @No
super.fillCompletionVariants(parameters, result);
}

private Collection<? extends LookupElement> toProposals(Project project, Editor editor, Document document,
private Collection<? extends LookupElement> toProposals(PsiFile file, Editor editor, Document document,
int offset, Either<List<CompletionItem>,
CompletionList> completion, LanguageServer languageServer) {
if (completion != null) {
List<CompletionItem> 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());
}
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);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String, String> variableResolver) {
public static @NotNull Template createTemplate(@NotNull String snippetContent, @NotNull Project project, @NotNull Function<String, String> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String, String> variableResolver) {
super(variableResolver);
public SnippetTemplateLoader(Template template, Function<String, String> variableResolver, LspSnippetIndentOptions indentOptions) {
super(variableResolver, indentOptions);
this.template = template;
this.existingVariables = new ArrayList<>();
}
Expand All @@ -58,7 +60,7 @@ public void endSnippet() {

@Override
public void text(String text) {
template.addTextSegment(text);
template.addTextSegment(formatText(text));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,28 @@ public abstract class AbstractLspSnippetHandler implements LspSnippetHandler {

private final Function<String, String> variableResolver;

public AbstractLspSnippetHandler(Function<String, String> 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<String, String> 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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ public class DefaultLspSnippetHandler extends AbstractLspSnippetHandler {

private final StringBuilder templateContent;

public DefaultLspSnippetHandler(Function<String, String> 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<String, String> variableResolver, LspSnippetIndentOptions indentOptions) {
super(variableResolver, indentOptions);
this.templateContent = new StringBuilder();
}

Expand All @@ -47,7 +53,7 @@ public void endSnippet() {

@Override
public void text(String text) {
appendContent(text);
appendContent(formatText(text));
}

@Override
Expand Down
Loading

0 comments on commit cb882e8

Please sign in to comment.