package org.intellij.plugins.markdown.lang.psi.impl; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.CompositePsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import org.intellij.plugins.markdown.lang.MarkdownTokenTypes; import org.intellij.plugins.markdown.lang.psi.MarkdownPsiElement; import org.intellij.plugins.markdown.structureView.MarkdownBasePresentation; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; public class MarkdownCodeFenceImpl extends CompositePsiElement implements PsiLanguageInjectionHost, MarkdownPsiElement { public MarkdownCodeFenceImpl(IElementType type) { super(type); } @Nullable public String getFenceLanguage() { final PsiElement element = findPsiChildByType(MarkdownTokenTypes.FENCE_LANG); if (element == null) { return null; } return element.getText(); } @Override public ItemPresentation getPresentation() { return new MarkdownBasePresentation() { @Nullable @Override public String getPresentableText() { if (!isValid()) { return null; } return "Code fence"; } @Nullable @Override public String getLocationString() { if (!isValid()) { return null; } final StringBuilder sb = new StringBuilder(); for (PsiElement child = getFirstChild(); child != null; child = child.getNextSibling()) { if (!(child instanceof MarkdownCodeFenceContentImpl)) { continue; } if (sb.length() > 0) { sb.append("\\n"); } sb.append(child.getText()); if (sb.length() >= MarkdownCompositePsiElementBase.PRESENTABLE_TEXT_LENGTH) { break; } } return sb.toString(); } }; } @Override public boolean isValidHost() { return true; } @Override public PsiLanguageInjectionHost updateText(@NotNull String text) { return ElementManipulators.handleContentChange(this, text); } @NotNull @Override public LiteralTextEscaper<? extends PsiLanguageInjectionHost> createLiteralTextEscaper() { return new LiteralTextEscaper<PsiLanguageInjectionHost>(this) { @Override public boolean decode(@NotNull TextRange rangeInsideHost, @NotNull StringBuilder outChars) { outChars.append(rangeInsideHost.substring(myHost.getText())); return true; } @Override public int getOffsetInHost(int offsetInDecoded, @NotNull TextRange rangeInsideHost) { return rangeInsideHost.getStartOffset() + offsetInDecoded; } @NotNull @Override public TextRange getRelevantTextRange() { return getContentTextRange(); } public TextRange getContentTextRange() { final MarkdownCodeFenceContentImpl first = PsiTreeUtil.findChildOfType(myHost, MarkdownCodeFenceContentImpl.class); if (first == null) { return TextRange.EMPTY_RANGE; } MarkdownCodeFenceContentImpl last = null; for (PsiElement child = myHost.getLastChild(); child != null; child = child.getPrevSibling()) { if (child instanceof MarkdownCodeFenceContentImpl) { last = ((MarkdownCodeFenceContentImpl)child); break; } } assert last != null; return TextRange.create(first.getStartOffsetInParent(), last.getStartOffsetInParent() + last.getTextLength()); } @Override public boolean isOneLine() { return false; } }; } @NotNull @Override public List<MarkdownPsiElement> getCompositeChildren() { return Collections.emptyList(); } public static class Manipulator extends AbstractElementManipulator<MarkdownCodeFenceImpl> { @Override public MarkdownCodeFenceImpl handleContentChange(@NotNull MarkdownCodeFenceImpl element, @NotNull TextRange range, String newContent) throws IncorrectOperationException { return null; } } }