src/com/intellij/idea/plugin/hybris/jsp/JspPropertyFoldingBuilder.java (153 lines of code) (raw):

/* * This file is part of "SAP Commerce Developers Toolset" plugin for IntelliJ IDEA. * Copyright (C) 2019-2024 EPAM Systems <hybrisideaplugin@epam.com> and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.intellij.idea.plugin.hybris.jsp; import com.intellij.idea.plugin.hybris.settings.components.DeveloperSettingsComponent; import com.intellij.lang.ASTNode; import com.intellij.lang.folding.FoldingBuilderEx; import com.intellij.lang.folding.FoldingDescriptor; import com.intellij.lang.properties.IProperty; import com.intellij.lang.properties.psi.PropertiesFile; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.psi.*; import com.intellij.psi.jsp.JspFile; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlFile; import com.intellij.util.ObjectUtils; import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; public class JspPropertyFoldingBuilder extends FoldingBuilderEx { private static final List<Locale.LanguageRange> ourPriorityList = Locale.LanguageRange.parse( "en-US;q=1.0,en;q=0.5,de;q=0.1" ); private static final Key<CachedValue<IProperty>> KEY = Key.create(JspPropertyFoldingBuilder.class + ":resolvedProperty"); @NotNull @Override public FoldingDescriptor[] buildFoldRegions( @NotNull final PsiElement root, @NotNull final Document document, final boolean quick ) { if (quick || !isFoldingEnabled(root.getProject())) { return FoldingDescriptor.EMPTY_ARRAY; } if (!(root instanceof XmlFile)) { return FoldingDescriptor.EMPTY_ARRAY; } if (root instanceof final JspFile jspFile) { final List<FoldingDescriptor> results = new SmartList<>(); jspFile.accept(new XmlRecursiveElementVisitor(true) { @Override public void visitXmlAttribute(@NotNull final XmlAttribute attribute) { if (!mayResolveToProperty(attribute)) { return; } final XmlAttributeValue value = attribute.getValueElement(); if (value != null) { Optional.ofNullable(JspPropertyFoldingBuilder.getResolvedProperty(value)) .map(property -> createFolding(value, property)) .ifPresent(results::add); super.visitXmlAttribute(attribute); } } private FoldingDescriptor createFolding( final @NotNull XmlAttributeValue value, final @NotNull IProperty property ) { return new FoldingDescriptor( value.getNode(), value.getValueTextRange(), null, Collections.singleton(property) ); } }); return results.toArray(new FoldingDescriptor[results.size()]); } return FoldingDescriptor.EMPTY_ARRAY; } protected boolean mayResolveToProperty(@NotNull final XmlAttribute xmlAttribute) { return "code".equals(xmlAttribute.getName()); } @Override public String getPlaceholderText(@NotNull final ASTNode node) { final PsiElement element = node.getPsi(); if (element instanceof XmlAttributeValue) { final IProperty property = getResolvedProperty((XmlAttributeValue) element); return property == null ? element.getText() : '{' + property.getValue() + '}'; } return element.getText(); } @Override public boolean isCollapsedByDefault(@NotNull final ASTNode node) { return isFoldingEnabled(node.getPsi().getProject()); } private boolean isFoldingEnabled(final @NotNull Project project) { return DeveloperSettingsComponent.getInstance(project).getState() .getImpexSettings() .getFolding() .getEnabled(); } @Nullable private static IProperty getResolvedProperty(@NotNull final XmlAttributeValue codeValue) { return CachedValuesManager.getCachedValue(codeValue, KEY, () -> { final List<IProperty> allProperties = new SmartList<>(); for (PsiReference nextRef : codeValue.getReferences()) { if (nextRef instanceof PsiPolyVariantReference) { Arrays.stream(((PsiPolyVariantReference) nextRef).multiResolve(false)) .filter(ResolveResult::isValidResult) .map(ResolveResult::getElement) .map(o -> ObjectUtils.tryCast(o, IProperty.class)) .filter(Objects::nonNull) .forEach(allProperties::add); } else { Optional.ofNullable(nextRef.resolve()) .map(o -> ObjectUtils.tryCast(o, IProperty.class)) .ifPresent(allProperties::add); } } final IProperty theChosenOne = chooseForLocale(allProperties); return new CachedValueProvider.Result<>(theChosenOne, PsiModificationTracker.MODIFICATION_COUNT); }); } private static IProperty chooseForLocale( final @NotNull List<IProperty> properties ) { return chooseForLocale(ourPriorityList, properties); } private static IProperty chooseForLocale( final @NotNull List<Locale.LanguageRange> priorityList, final @NotNull List<IProperty> properties ) { if (properties.isEmpty()) { return null; } final IProperty first = properties.get(0); if (properties.size() == 1) { return first; } final Map<Locale, IProperty> map = new HashMap<>(); final List<Locale> locales = new LinkedList<>(); for (IProperty nextProperty : properties) { final Locale nextLocale = safeGetLocale(nextProperty); if (nextLocale != null) { map.put(nextLocale, nextProperty); locales.add(nextLocale); } } final Locale best = Locale.lookup(priorityList, locales); //System.err.println("found locales: " + locales + ", best: " + best + ", result: " + map.get(best)); return Optional.ofNullable(best).map(map::get).orElse(first); } private static Locale safeGetLocale(final @NotNull IProperty property) { try { final PropertiesFile file = property.getPropertiesFile(); return file == null ? null : file.getLocale(); } catch (PsiInvalidElementAccessException e) { return null; } } }