| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.psi.formatter.common; |
| |
| import com.intellij.formatting.*; |
| import com.intellij.lang.ASTNode; |
| import com.intellij.lang.Language; |
| import com.intellij.lang.LanguageFormatting; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiLanguageInjectionHost; |
| import com.intellij.psi.codeStyle.CodeStyleSettings; |
| import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.List; |
| |
| /** |
| * @author nik |
| */ |
| public abstract class InjectedLanguageBlockBuilder { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.formatter.xml.XmlInjectedLanguageBlockBuilder"); |
| |
| public Block createInjectedBlock(ASTNode node, |
| Block originalBlock, |
| Indent indent, |
| int offset, |
| TextRange range, |
| @Nullable Language language) |
| { |
| return new InjectedLanguageBlockWrapper(originalBlock, offset, range, indent, language); |
| } |
| |
| public abstract CodeStyleSettings getSettings(); |
| |
| public abstract boolean canProcessFragment(String text, ASTNode injectionHost); |
| |
| public abstract Block createBlockBeforeInjection(ASTNode node, Wrap wrap, Alignment alignment, Indent indent, TextRange range); |
| |
| public abstract Block createBlockAfterInjection(ASTNode node, Wrap wrap, Alignment alignment, Indent indent, TextRange range); |
| |
| public boolean addInjectedBlocks(List<Block> result, final ASTNode injectionHost, Wrap wrap, Alignment alignment, Indent indent) { |
| final PsiFile[] injectedFile = new PsiFile[1]; |
| final Ref<TextRange> injectedRangeInsideHost = new Ref<TextRange>(); |
| final Ref<Integer> prefixLength = new Ref<Integer>(); |
| final Ref<Integer> suffixLength = new Ref<Integer>(); |
| final Ref<ASTNode> injectionHostToUse = new Ref<ASTNode>(injectionHost); |
| |
| final PsiLanguageInjectionHost.InjectedPsiVisitor injectedPsiVisitor = new PsiLanguageInjectionHost.InjectedPsiVisitor() { |
| @Override |
| public void visit(@NotNull final PsiFile injectedPsi, @NotNull final List<PsiLanguageInjectionHost.Shred> places) { |
| if (places.size() != 1) { |
| return; |
| } |
| final PsiLanguageInjectionHost.Shred shred = places.get(0); |
| TextRange textRange = shred.getRangeInsideHost(); |
| PsiLanguageInjectionHost shredHost = shred.getHost(); |
| if (shredHost == null) { |
| return; |
| } |
| ASTNode node = shredHost.getNode(); |
| if (node == null) { |
| return; |
| } |
| if (node != injectionHost) { |
| int shift = 0; |
| boolean canProcess = false; |
| for (ASTNode n = injectionHost.getTreeParent(), prev = injectionHost; n != null; prev = n, n = n.getTreeParent()) { |
| shift += n.getStartOffset() - prev.getStartOffset(); |
| if (n == node) { |
| textRange = textRange.shiftRight(shift); |
| canProcess = true; |
| break; |
| } |
| } |
| if (!canProcess) { |
| return; |
| } |
| } |
| |
| String childText; |
| if ((injectionHost.getTextLength() == textRange.getEndOffset() && textRange.getStartOffset() == 0) || |
| (canProcessFragment((childText = injectionHost.getText()).substring(0, textRange.getStartOffset()), injectionHost) && |
| canProcessFragment(childText.substring(textRange.getEndOffset()), injectionHost))) { |
| injectedFile[0] = injectedPsi; |
| injectedRangeInsideHost.set(textRange); |
| prefixLength.set(shred.getPrefix().length()); |
| suffixLength.set(shred.getSuffix().length()); |
| } |
| } |
| }; |
| final PsiElement injectionHostPsi = injectionHost.getPsi(); |
| InjectedLanguageUtil.enumerate(injectionHostPsi, injectionHostPsi.getContainingFile(), false, injectedPsiVisitor); |
| |
| if (injectedFile[0] != null) { |
| final Language childLanguage = injectedFile[0].getLanguage(); |
| final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(childLanguage, injectionHostPsi); |
| |
| if (builder != null) { |
| final int startOffset = injectedRangeInsideHost.get().getStartOffset(); |
| final int endOffset = injectedRangeInsideHost.get().getEndOffset(); |
| TextRange range = injectionHostToUse.get().getTextRange(); |
| |
| int childOffset = range.getStartOffset(); |
| if (startOffset != 0) { |
| final ASTNode leaf = injectionHostToUse.get().findLeafElementAt(startOffset - 1); |
| result.add(createBlockBeforeInjection(leaf, wrap, alignment, indent, new TextRange(childOffset, childOffset + startOffset))); |
| } |
| |
| addInjectedLanguageBlockWrapper(result, injectedFile[0].getNode(), indent, childOffset + startOffset, |
| new TextRange(prefixLength.get(), injectedFile[0].getTextLength() - suffixLength.get())); |
| |
| if (endOffset != injectionHostToUse.get().getTextLength()) { |
| final ASTNode leaf = injectionHostToUse.get().findLeafElementAt(endOffset); |
| result.add(createBlockAfterInjection(leaf, wrap, alignment, indent, new TextRange(childOffset + endOffset, range.getEndOffset()))); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public void addInjectedLanguageBlockWrapper(final List<Block> result, final ASTNode injectedNode, |
| final Indent indent, int offset, @Nullable TextRange range) { |
| |
| // |
| // Do not create a block for an empty range |
| // |
| if (range != null) { |
| if (range.getLength() == 0) return; |
| if(StringUtil.isEmptyOrSpaces(range.substring(injectedNode.getText()))) { |
| return; |
| } |
| } |
| |
| final PsiElement childPsi = injectedNode.getPsi(); |
| final Language childLanguage = childPsi.getLanguage(); |
| final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(childLanguage, childPsi); |
| LOG.assertTrue(builder != null); |
| final FormattingModel childModel = builder.createModel(childPsi, getSettings()); |
| Block original = childModel.getRootBlock(); |
| |
| if ((original.isLeaf() && !injectedNode.getText().trim().isEmpty()) || !original.getSubBlocks().isEmpty()) { |
| result.add(createInjectedBlock(injectedNode, original, indent, offset, range, childLanguage)); |
| } |
| } |
| } |