blob: 2aca5a5280a4c7d1dc86e4b99f46b12b904587a0 [file] [log] [blame]
/*
* 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);
}
}
}