package com.intellij.javascript.flex.css;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.css.*;
import com.intellij.psi.css.reference.CssReference;
import com.intellij.psi.css.resolve.CssElementProcessor;
import com.intellij.psi.css.resolve.CssResolveManager;
import com.intellij.psi.css.resolve.impl.CssResolverImpl;
import com.intellij.psi.impl.source.resolve.reference.impl.PsiPolyVariantCachingReference;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class CssClassValueReference extends PsiPolyVariantCachingReference implements CssReference {
private final PsiElement myElement;
private final int myStart;
private final int myEnd;
public CssClassValueReference(@NotNull PsiElement element) {
myElement = element;
String value = getValue(myElement);
int length = value != null ? value.length() : 0;
if (length == 0) {
myStart = 0;
myEnd = 0;
}
else if (element instanceof CssString || FlexCssUtil.inQuotes(myElement.getText())) {
final String text = myElement.getText();
myStart = text.length() >= 2 && text.charAt(1) == '.' ? 2 : 1;
myEnd = length + 1;
}
else {
myStart = 0;
myEnd = length;
}
}
public static String getValue(PsiElement element) {
if (element instanceof CssString) {
return ((CssString)element).getValue();
}
else {
String text = element.getText();
if (FlexCssUtil.inQuotes(text)) {
return text.substring(text.length() >= 2 && text.charAt(1) == '.' ? 2 : 1, text.length() - 1);
}
else {
return text;
}
}
}
@NotNull
@Override
public String getUnresolvedMessagePattern() {
return CssBundle.message("invalid.css.class");
}
@Override
public PsiElement getElement() {
return myElement;
}
@Override
public TextRange getRangeInElement() {
return new TextRange(myStart, myEnd);
}
@Override
@NotNull
public String getCanonicalText() {
String value = getValue(myElement);
return value != null ? value : "";
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
final ElementManipulator<PsiElement> manipulator = ElementManipulators.getManipulator(myElement);
assert manipulator != null;
return manipulator.handleContentChange(myElement, getRangeInElement(), newElementName);
}
@Override
@Nullable
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
return null;
}
@Override
@NotNull
public Object[] getVariants() {
MyCandidatesProcessor processor = new MyCandidatesProcessor();
processStyles(processor);
return processor.myStyleNames.toArray();
}
@NotNull
@Override
protected ResolveResult[] resolveInner(boolean incompleteCode, @NotNull PsiFile containingFile) {
String value = getValue(myElement);
if (value == null) return ResolveResult.EMPTY_ARRAY;
MyResolveProcessor processor = new MyResolveProcessor(value);
processStyles(processor);
if (processor.myTargets.isEmpty()) {
return ResolveResult.EMPTY_ARRAY;
}
return PsiElementResolveResult.createResults(processor.myTargets);
}
@Override
public boolean isReferenceTo(PsiElement element) {
if (element instanceof CssSelectorSuffix) {
String text = element.getText();
return text != null && !text.isEmpty() && text.substring(1).equals(getValue(myElement));
}
return false;
}
@Override
public boolean isSoft() {
return true;
}
private void processStyles(CssElementProcessor processor) {
PsiFile file = myElement.getContainingFile();
if (!(file instanceof XmlFile)) {
PsiElement context = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file);
if (context instanceof XmlFile) {
file = (XmlFile)context;
}
}
if (file instanceof XmlFile) {
CssResolveManager.getInstance().getNewResolver().processOneFile((XmlFile)file, processor, true);
}
else if (file instanceof StylesheetFile) {
processOneStylesheetFile((StylesheetFile)file, processor);
}
Module module = ModuleUtilCore.findModuleForPsiElement(file);
if (module != null) {
CssResolverImpl.processStyles(module, processor, file);
}
}
private static void processOneStylesheetFile(@NotNull StylesheetFile file, @NotNull CssElementProcessor processor) {
CssStylesheet stylesheet = file.getStylesheet();
if (stylesheet != null) {
for (CssRuleset ruleset : stylesheet.getRulesets()) {
processor.process(ruleset);
}
}
}
private static class MyCandidatesProcessor extends MyCssElementProcessor {
Set<String> myStyleNames = ContainerUtil.newLinkedHashSet();
@Override
protected void handleSelector(@NotNull CssSelectorSuffix selectorSuffix, @NotNull String text) {
myStyleNames.add(text);
}
}
private static class MyResolveProcessor extends MyCssElementProcessor {
private final String myReferenceText;
private final Set<CssSelectorSuffix> myTargets = ContainerUtil.newLinkedHashSet();
private MyResolveProcessor(@NotNull String referenceText) {
myReferenceText = referenceText;
}
@Override
protected void handleSelector(@NotNull CssSelectorSuffix selectorSuffix, @NotNull String text) {
if (text.equals(myReferenceText)) {
myTargets.add(selectorSuffix);
}
}
}
private abstract static class MyCssElementProcessor extends CssElementProcessor {
@Override
public boolean process(@NotNull CssRuleset ruleset) {
for (CssSelector selector : ruleset.getSelectors()) {
for (PsiElement child : selector.getChildren()) {
if (child instanceof CssSimpleSelector) {
for (CssSelectorSuffix selectorSuffix : ((CssSimpleSelector)child).getSelectorSuffixes()) {
String text = selectorSuffix.getText();
if (text != null && !text.isEmpty() && text.charAt(0) == '.') {
handleSelector(selectorSuffix, text.substring(1));
ProgressIndicatorProvider.checkCanceled();
}
}
}
}
}
return true;
}
protected abstract void handleSelector(@NotNull CssSelectorSuffix selectorSuffix, @NotNull String selectorName);
}
}