| /* |
| * Copyright 2000-2009 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.impl.source.tree.injected; |
| |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.LiteralTextEscaper; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiLanguageInjectionHost; |
| import com.intellij.psi.TokenType; |
| import com.intellij.psi.impl.source.tree.ForeignLeafPsiElement; |
| import com.intellij.psi.impl.source.tree.LeafElement; |
| import com.intellij.psi.impl.source.tree.RecursiveTreeElementWalkingVisitor; |
| import gnu.trove.THashMap; |
| |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @author cdr |
| */ |
| class LeafPatcher extends RecursiveTreeElementWalkingVisitor { |
| private LeafElement prevElement; |
| private String prevElementTail; |
| private int shredNo; |
| private String hostText; |
| private TextRange rangeInHost; |
| private final Place myShreds; |
| private final List<LiteralTextEscaper<? extends PsiLanguageInjectionHost>> myEscapers; |
| final Map<LeafElement, String> newTexts = new THashMap<LeafElement, String>(); |
| final StringBuilder catLeafs = new StringBuilder(); |
| |
| LeafPatcher(Place shreds, List<LiteralTextEscaper<? extends PsiLanguageInjectionHost>> escapers) { |
| myShreds = shreds; |
| myEscapers = escapers; |
| } |
| |
| @Override |
| public void visitLeaf(LeafElement leaf) { |
| String leafText = leaf instanceof ForeignLeafPsiElement ? "" : leaf.getText(); |
| catLeafs.append(leafText); |
| final TextRange leafRange = leaf.getTextRange(); |
| |
| StringBuilder leafEncodedText = constructTextFromHostPSI(leafRange.getStartOffset(), leafRange.getEndOffset()); |
| |
| if (leaf.getElementType() == TokenType.WHITE_SPACE && prevElementTail != null) { |
| // optimization: put all garbage into whitespace |
| leafEncodedText.insert(0, prevElementTail); |
| newTexts.remove(prevElement); |
| storeUnescapedTextFor(prevElement, null); |
| } |
| if (!Comparing.equal(leafText, leafEncodedText)) { |
| newTexts.put(leaf, leafEncodedText.toString()); |
| storeUnescapedTextFor(leaf, leafText); |
| } |
| prevElementTail = StringUtil.startsWith(leafEncodedText, leafText) && leafEncodedText.length() != leafText.length() ? |
| leafEncodedText.substring(leafText.length()) : null; |
| prevElement = leaf; |
| } |
| |
| private StringBuilder constructTextFromHostPSI(int startOffset, int endOffset) { |
| boolean firstTimer = false; |
| PsiLanguageInjectionHost.Shred current = myShreds.get(shredNo); |
| if (hostText == null) { |
| hostText = current.getHost().getText(); |
| rangeInHost = current.getRangeInsideHost(); |
| firstTimer = true; |
| } |
| |
| StringBuilder text = new StringBuilder(endOffset-startOffset); |
| while (startOffset < endOffset) { |
| TextRange shredRange = current.getRange(); |
| String prefix = current.getPrefix(); |
| if (startOffset >= shredRange.getEndOffset()) { |
| current = myShreds.get(++shredNo); |
| hostText = current.getHost().getText(); |
| rangeInHost = current.getRangeInsideHost(); |
| firstTimer = true; |
| continue; |
| } |
| assert startOffset >= shredRange.getStartOffset(); |
| if (startOffset - shredRange.getStartOffset() < prefix.length()) { |
| // inside prefix |
| TextRange rangeInPrefix = new TextRange(startOffset - shredRange.getStartOffset(), Math.min(prefix.length(), endOffset - shredRange.getStartOffset())); |
| text.append(prefix, rangeInPrefix.getStartOffset(), rangeInPrefix.getEndOffset()); |
| startOffset += rangeInPrefix.getLength(); |
| continue; |
| } |
| |
| String suffix = current.getSuffix(); |
| if (startOffset < shredRange.getEndOffset() - suffix.length()) { |
| // inside host body, cut out from the host text |
| int startOffsetInHost = myEscapers.get(shredNo).getOffsetInHost( |
| startOffset - shredRange.getStartOffset() - prefix.length(), rangeInHost); |
| int endOffsetCut = Math.min(endOffset, shredRange.getEndOffset() - suffix.length()); |
| int endOffsetInHost = myEscapers.get(shredNo).getOffsetInHost( |
| endOffsetCut - shredRange.getStartOffset() - prefix.length(), rangeInHost); |
| if (endOffsetInHost != -1) { |
| if (firstTimer ) text.append(hostText, rangeInHost.getStartOffset(), startOffsetInHost); |
| text.append(hostText, startOffsetInHost, endOffsetInHost); |
| startOffset = endOffsetCut; |
| // todo what about lastTimer? |
| continue; |
| } |
| } |
| |
| // inside suffix |
| TextRange rangeInSuffix = new TextRange(suffix.length() - shredRange.getEndOffset() + startOffset, Math.min(suffix.length(), endOffset + suffix.length() - shredRange.getEndOffset())); |
| text.append(suffix, rangeInSuffix.getStartOffset(), rangeInSuffix.getEndOffset()); |
| startOffset += rangeInSuffix.getLength(); |
| } |
| |
| return text; |
| } |
| |
| static final Key<String> UNESCAPED_TEXT = Key.create("INJECTED_UNESCAPED_TEXT"); |
| private static void storeUnescapedTextFor(final LeafElement leaf, final String leafText) { |
| PsiElement psi = leaf.getPsi(); |
| if (psi != null) { |
| psi.putCopyableUserData(UNESCAPED_TEXT, leafText); |
| } |
| } |
| } |