package org.intellij.plugins.markdown.injection; import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.codeInsight.lookup.LookupElementDecorator; import com.intellij.lang.Language; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.DeferredIconImpl; import org.intellij.plugins.markdown.lang.MarkdownElementTypes; import org.intellij.plugins.markdown.lang.MarkdownTokenTypes; import org.intellij.plugins.markdown.lang.psi.impl.MarkdownFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; public class LanguageListCompletionContributor extends CompletionContributor { @Override public void beforeCompletion(@NotNull CompletionInitializationContext context) { if (context.getFile() instanceof MarkdownFile) { context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER + "\n"); } } @Override public boolean invokeAutoPopup(@NotNull PsiElement position, char typeChar) { return typeChar == '`' && position.getNode().getElementType() == MarkdownTokenTypes.CODE_FENCE_START; } @Override public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { final PsiElement completionElement = parameters.getPosition(); if (PsiUtilCore.getElementType(completionElement) == MarkdownTokenTypes.FENCE_LANG) { doFillVariants(parameters, result); } } private static void doFillVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { for (CodeFenceLanguageProvider provider : LanguageGuesser.INSTANCE.getCodeFenceLanguageProviders()) { final List<LookupElement> lookups = provider.getCompletionVariantsForInfoString(parameters); for (LookupElement lookupElement : lookups) { result.addElement(LookupElementDecorator.withInsertHandler(lookupElement, (context, item) -> { new MyInsertHandler(parameters).handleInsert(context, item); lookupElement.handleInsert(context); })); } } for (Map.Entry<String, Language> entry : LanguageGuesser.INSTANCE.getLangToLanguageMap().entrySet()) { final Language language = entry.getValue(); final LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(entry.getKey()).withIcon(new DeferredIconImpl<>(null, language, true, language1 -> { final LanguageFileType fileType = language1.getAssociatedFileType(); return fileType != null ? fileType.getIcon() : null; })) .withTypeText(language.getDisplayName(), true) .withInsertHandler(new MyInsertHandler(parameters)); result.addElement(lookupElementBuilder); } } public static boolean isInMiddleOfUncollapsedFence(@Nullable PsiElement element, int offset) { if (element == null) { return false; } if (PsiUtilCore.getElementType(element) == MarkdownTokenTypes.CODE_FENCE_START) { final TextRange range = element.getTextRange(); return range.getStartOffset() + range.getEndOffset() == offset * 2; } if (PsiUtilCore.getElementType(element) == MarkdownTokenTypes.TEXT && PsiUtilCore.getElementType(element.getParent()) == MarkdownElementTypes.CODE_SPAN) { final TextRange range = element.getTextRange(); final TextRange parentRange = element.getParent().getTextRange(); return range.getStartOffset() - parentRange.getStartOffset() == parentRange.getEndOffset() - range.getEndOffset(); } return false; } private static class MyInsertHandler implements InsertHandler<LookupElement> { private final CompletionParameters myParameters; public MyInsertHandler(CompletionParameters parameters) { myParameters = parameters; } @Override public void handleInsert(InsertionContext context, LookupElement item) { if (isInMiddleOfUncollapsedFence(myParameters.getOriginalPosition(), context.getStartOffset())) { context.getDocument().insertString(context.getTailOffset(), "\n\n"); context.getEditor().getCaretModel().moveCaretRelatively(1, 0, false, false, false); } } } }